feat: implement HAR conversion

This commit is contained in:
Andrew Bastin
2021-12-28 13:48:23 +05:30
parent 1baf73a710
commit 1be2c12062
4 changed files with 286 additions and 10 deletions

View File

@@ -0,0 +1,107 @@
import * as Har from "har-format"
import { HoppRESTRequest } from "@hoppscotch/data"
import { FieldEquals, objectFieldIncludes } from "../typeutils"
// We support HAR Spec 1.2
// For more info on the spec: http://www.softwareishard.com/blog/har-12-spec/
const buildHarHeaders = (req: HoppRESTRequest): Har.Header[] => {
return req.headers
.filter((header) => header.active)
.map((header) => ({
name: header.key,
value: header.value,
}))
}
const buildHarQueryStrings = (req: HoppRESTRequest): Har.QueryString[] => {
return req.params
.filter((param) => param.active)
.map((param) => ({
name: param.key,
value: param.value,
}))
}
const buildHarPostParams = (
req: HoppRESTRequest &
FieldEquals<HoppRESTRequest, "method", ["POST", "PUT"]> & {
body: {
contentType: "application/x-www-form-urlencoded" | "multipart/form-data"
}
}
): Har.Param[] => {
// URL Encoded strings have a string style of contents
if (req.body.contentType === "application/x-www-form-urlencoded") {
return req.body.body
.split("&") // Split by separators
.map((keyValue) => {
const [key, value] = keyValue.split("=")
return {
name: key,
value,
}
})
} else {
// FormData has its own format
return req.body.body.flatMap((entry) => {
if (entry.isFile) {
// We support multiple files
return entry.value.map(
(file) =>
<Har.Param>{
name: entry.key,
fileName: entry.key, // TODO: Blob doesn't contain file info, anyway to bring file name here ?
contentType: file.type,
}
)
} else {
return {
name: entry.key,
value: entry.value,
}
}
})
}
}
const buildHarPostData = (req: HoppRESTRequest): Har.PostData | undefined => {
if (!req.body.contentType) return undefined
if (!objectFieldIncludes(req, "method", ["POST", "PUT"] as const))
return undefined
if (
objectFieldIncludes(req.body, "contentType", [
"application/x-www-form-urlencoded",
"multipart/form-data",
] as const)
) {
return {
mimeType: req.body.contentType, // By default assume JSON ?
params: buildHarPostParams(req as any),
}
} else {
if (!req.body.contentType) return undefined
return {
mimeType: req.body.contentType, // Let's assume by default content type is JSON
text: req.body.body,
}
}
}
export const buildHarRequest = (req: HoppRESTRequest): Har.Request => {
return {
bodySize: -1, // TODO: It would be cool if we can calculate the body size
headersSize: -1, // TODO: It would be cool if we can calculate the header size
httpVersion: "HTTP/1.1",
cookies: [], // Hoppscotch does not have formal support for Cookies as of right now
headers: buildHarHeaders(req),
method: req.method,
queryString: buildHarQueryStrings(req),
url: req.endpoint,
postData: buildHarPostData(req),
}
}

View File

@@ -0,0 +1,17 @@
export type FieldEquals<T, K extends keyof T, Vals extends T[K][]> = {
// eslint-disable-next-line
[_x in K]: Vals[number]
}
export const objectFieldIncludes = <
T,
K extends keyof T,
V extends readonly T[K][]
>(
obj: T,
field: K,
values: V
// eslint-disable-next-line
): obj is T & { [_x in K]: V[number] } => {
return values.includes(obj[field])
}

View File

@@ -81,6 +81,7 @@
"graphql-language-service-interface": "^2.9.1",
"graphql-language-service-parser": "^1.10.4",
"graphql-tag": "^2.12.6",
"httpsnippet": "^2.0.0",
"io-ts": "^2.2.16",
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
@@ -135,6 +136,8 @@
"@types/codemirror": "^5.60.5",
"@types/cookie": "^0.4.1",
"@types/esprima": "^4.0.3",
"@types/har-format": "^1.2.8",
"@types/httpsnippet": "^1.23.1",
"@types/lodash": "^4.14.177",
"@types/splitpanes": "^2.2.1",
"@types/uuid": "^8.3.3",

169
pnpm-lock.yaml generated
View File

@@ -100,6 +100,8 @@ importers:
'@types/codemirror': ^5.60.5
'@types/cookie': ^0.4.1
'@types/esprima': ^4.0.3
'@types/har-format': ^1.2.8
'@types/httpsnippet': ^1.23.1
'@types/lodash': ^4.14.177
'@types/splitpanes': ^2.2.1
'@types/uuid': ^8.3.3
@@ -129,6 +131,7 @@ importers:
graphql-language-service-interface: ^2.9.1
graphql-language-service-parser: ^1.10.4
graphql-tag: ^2.12.6
httpsnippet: ^2.0.0
io-ts: ^2.2.16
jest: ^27.4.5
jest-serializer-vue: ^2.0.2
@@ -219,6 +222,7 @@ importers:
graphql-language-service-interface: 2.9.1_graphql@15.7.2+typescript@4.5.4
graphql-language-service-parser: 1.10.4_graphql@15.7.2+typescript@4.5.4
graphql-tag: 2.12.6_graphql@15.7.2
httpsnippet: 2.0.0
io-ts: 2.2.16_fp-ts@2.11.5
json-loader: 0.5.7
lodash: 4.17.21
@@ -272,6 +276,8 @@ importers:
'@types/codemirror': 5.60.5
'@types/cookie': 0.4.1
'@types/esprima': 4.0.3
'@types/har-format': 1.2.8
'@types/httpsnippet': 1.23.1
'@types/lodash': 4.14.178
'@types/splitpanes': 2.2.1
'@types/uuid': 8.3.3
@@ -4635,6 +4641,10 @@ packages:
'@types/node': 17.0.5
dev: true
/@types/har-format/1.2.8:
resolution: {integrity: sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==}
dev: true
/@types/html-minifier-terser/5.1.2:
resolution: {integrity: sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==}
dev: false
@@ -4657,6 +4667,12 @@ packages:
'@types/node': 16.11.12
dev: false
/@types/httpsnippet/1.23.1:
resolution: {integrity: sha512-i8PkOoRuOBunHpIs07aB55eqqXlFxZD8Q37UemJ2nCFK+x1dagJtrQzEvsbseefqHmW6Z9mJl834jY+ktm3FLA==}
dependencies:
'@types/har-format': 1.2.8
dev: true
/@types/istanbul-lib-coverage/2.0.3:
resolution: {integrity: sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==}
dev: true
@@ -5634,7 +5650,7 @@ packages:
resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.33
mime-types: 2.1.34
negotiator: 0.6.2
dev: false
@@ -5978,7 +5994,6 @@ packages:
/asynckit/0.4.0:
resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}
dev: true
/at-least-node/1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
@@ -7194,7 +7209,6 @@ packages:
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -7254,7 +7268,7 @@ packages:
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.50.0
mime-db: 1.51.0
dev: false
/compression/1.7.4:
@@ -8057,7 +8071,6 @@ packages:
/delayed-stream/1.0.0:
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
engines: {node: '>=0.4.0'}
dev: true
/delegates/1.0.0:
resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}
@@ -9121,6 +9134,18 @@ packages:
resolution: {integrity: sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=}
engines: {node: '>= 0.6'}
/event-stream/3.3.4:
resolution: {integrity: sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=}
dependencies:
duplexer: 0.1.2
from: 0.1.7
map-stream: 0.1.0
pause-stream: 0.0.11
split: 0.3.3
stream-combiner: 0.0.4
through: 2.3.8
dev: false
/event-target-shim/5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -9600,6 +9625,15 @@ packages:
/form-data-encoder/1.7.1:
resolution: {integrity: sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==}
/form-data/3.0.0:
resolution: {integrity: sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.34
dev: false
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
@@ -9639,6 +9673,10 @@ packages:
resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=}
engines: {node: '>= 0.6'}
/from/0.1.7:
resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=}
dev: false
/from2/2.3.0:
resolution: {integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=}
dependencies:
@@ -9686,6 +9724,12 @@ packages:
/fs-monkey/1.0.3:
resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==}
/fs-readfile-promise/2.0.1:
resolution: {integrity: sha1-gAI4I5gfn//+AWCei+Zo9prknnA=}
dependencies:
graceful-fs: 4.2.8
dev: false
/fs-write-stream-atomic/1.0.10:
resolution: {integrity: sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=}
dependencies:
@@ -9695,6 +9739,16 @@ packages:
readable-stream: 2.3.7
dev: false
/fs-writefile-promise/1.0.3:
resolution: {integrity: sha1-4C+bWP/CVe2CKtx6ARFPRF1I0GM=}
engines: {node: '>=0.10'}
dependencies:
mkdirp-promise: 1.1.0
pinkie-promise: 1.0.0
transitivePeerDependencies:
- mkdirp
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
@@ -9757,6 +9811,10 @@ packages:
has: 1.0.3
has-symbols: 1.0.2
/get-own-enumerable-property-symbols/3.0.2:
resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
dev: false
/get-package-type/0.1.0:
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
engines: {node: '>=8.0.0'}
@@ -10136,6 +10194,20 @@ packages:
resolution: {integrity: sha512-7+G0/2/COR8pwteYFqHIVYfQpuEiO2HXwJrhCBJVgrNrl9O5eaUoJVDGXUJX+0RpGncNVTuestexjk1afj01wQ==}
dev: false
/har-schema/2.0.0:
resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=}
engines: {node: '>=4'}
dev: false
/har-validator/5.1.5:
resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}
engines: {node: '>=6'}
deprecated: this library is no longer supported
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
dev: false
/hard-rejection/2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
@@ -10515,6 +10587,24 @@ packages:
transitivePeerDependencies:
- supports-color
/httpsnippet/2.0.0:
resolution: {integrity: sha512-Hb2ttfB5OhasYxwChZ8QKpYX3v4plNvwMaMulUIC7M3RHRDf1Op6EMp47LfaU2sgQgfvo5spWK4xRAirMEisrg==}
engines: {node: '>=10'}
hasBin: true
dependencies:
chalk: 1.1.3
commander: 2.20.3
debug: 2.6.9
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.1.5
stringify-object: 3.3.0
transitivePeerDependencies:
- mkdirp
dev: false
/human-signals/2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -10986,6 +11076,11 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
/is-obj/1.0.1:
resolution: {integrity: sha1-PkcprB9f3gJc19g6iW2rn09n2w8=}
engines: {node: '>=0.10.0'}
dev: false
/is-obj/2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
@@ -11037,6 +11132,11 @@ packages:
call-bind: 1.0.2
has-tostringtag: 1.0.0
/is-regexp/1.0.0:
resolution: {integrity: sha1-/S2INUXEa6xaYz57mgnof6LLUGk=}
engines: {node: '>=0.10.0'}
dev: false
/is-regexp/2.1.0:
resolution: {integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==}
engines: {node: '>=6'}
@@ -12541,6 +12641,10 @@ packages:
engines: {node: '>=8'}
dev: true
/map-stream/0.1.0:
resolution: {integrity: sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=}
dev: false
/map-visit/1.0.0:
resolution: {integrity: sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=}
engines: {node: '>=0.10.0'}
@@ -12729,7 +12833,6 @@ packages:
/mime-db/1.51.0:
resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==}
engines: {node: '>= 0.6'}
dev: true
/mime-types/2.1.33:
resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==}
@@ -12742,7 +12845,6 @@ packages:
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.51.0
dev: true
/mime/1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
@@ -12872,6 +12974,14 @@ packages:
for-in: 1.0.2
is-extendable: 1.0.1
/mkdirp-promise/1.1.0:
resolution: {integrity: sha1-LISJPtZ24NmPsY+5piEv0bK5qBk=}
engines: {node: '>=4'}
deprecated: This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.
peerDependencies:
mkdirp: '>=0.5.0'
dev: false
/mkdirp/0.5.5:
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
hasBin: true
@@ -13131,7 +13241,7 @@ packages:
dependencies:
destroy: 1.0.4
etag: 1.8.1
mime-types: 2.1.33
mime-types: 2.1.34
on-finished: 2.3.0
vary: 1.1.2
dev: false
@@ -13783,6 +13893,12 @@ packages:
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
dev: true
/pause-stream/0.0.11:
resolution: {integrity: sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=}
dependencies:
through: 2.3.8
dev: false
/pbkdf2/3.1.2:
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
engines: {node: '>=0.12'}
@@ -13828,6 +13944,18 @@ packages:
engines: {node: '>=10'}
dev: false
/pinkie-promise/1.0.0:
resolution: {integrity: sha1-0dpn9UglY7t89X8oauKCLs+/NnA=}
engines: {node: '>=0.10.0'}
dependencies:
pinkie: 1.0.0
dev: false
/pinkie/1.0.0:
resolution: {integrity: sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=}
engines: {node: '>=0.10.0'}
dev: false
/pirates/4.0.4:
resolution: {integrity: sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==}
engines: {node: '>= 6'}
@@ -15333,7 +15461,7 @@ packages:
rollup: 2.62.0
typescript: 4.5.4
optionalDependencies:
'@babel/code-frame': 7.16.0
'@babel/code-frame': 7.16.7
dev: true
/rollup-plugin-ts/2.0.4_rollup@2.62.0+typescript@4.5.4:
@@ -16048,6 +16176,12 @@ packages:
dependencies:
extend-shallow: 3.0.2
/split/0.3.3:
resolution: {integrity: sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=}
dependencies:
through: 2.3.8
dev: false
/split2/3.2.2:
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
dependencies:
@@ -16126,6 +16260,12 @@ packages:
readable-stream: 2.3.7
dev: false
/stream-combiner/0.0.4:
resolution: {integrity: sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=}
dependencies:
duplexer: 0.1.2
dev: false
/stream-each/1.2.3:
resolution: {integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==}
dependencies:
@@ -16236,6 +16376,15 @@ packages:
dependencies:
safe-buffer: 5.2.1
/stringify-object/3.3.0:
resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
engines: {node: '>=4'}
dependencies:
get-own-enumerable-property-symbols: 3.0.2
is-obj: 1.0.1
is-regexp: 1.0.0
dev: false
/strip-ansi/3.0.1:
resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=}
engines: {node: '>=0.10.0'}
@@ -17885,7 +18034,7 @@ packages:
colorette: 1.4.0
mem: 8.1.1
memfs: 3.3.0
mime-types: 2.1.33
mime-types: 2.1.34
range-parser: 1.2.1
schema-utils: 3.1.1
webpack: 4.46.0