Skip to content

Commit

Permalink
feat: refactor curlify to snippets plugin
Browse files Browse the repository at this point in the history
Signed-off-by: mathis-m <mathis.michel@outlook.de>
  • Loading branch information
mathis-m committed Feb 8, 2021
1 parent 09a4bd6 commit 5d4c05a
Show file tree
Hide file tree
Showing 9 changed files with 475 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/core/components/live-response.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export default class LiveResponse extends React.Component {
return <span className="headerline" key={key}> {key}: {joinedHeaders} </span>
})
const hasHeaders = returnObject.length !== 0

const RequestSnippets = getComponent("RequestSnippets", true)
return (
<div>
{ curlRequest && <Curl request={ curlRequest } getConfigs={ getConfigs } /> }
{ curlRequest && <RequestSnippets request={ curlRequest }/> }
{ url && <div>
<h4>Request URL</h4>
<div className="request-url">
Expand Down
54 changes: 35 additions & 19 deletions src/core/curlify.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,60 +16,76 @@ const extractKey = (k) => {
}

export default function curl( request ){
let curlified = []
let curlified = ""
const addWords = (...args) => curlified += " " + args.join(" ")
const addWordsWithoutLeadingSpace = (...args) => curlified += args.join(" ")
const addNewLine = () => curlified += " \\\n"
const addIndent = (level = 1) => curlified += " ".repeat(level)
let isMultipartFormDataRequest = false
let headers = request.get("headers")
curlified.push( "curl" )
curlified += "curl"

if (request.get("curlOptions")) {
curlified.push(...request.get("curlOptions"))
addWords(...request.get("curlOptions"))
}

curlified.push( "-X", request.get("method") )
curlified.push( `"${request.get("url")}"`)
addWords("-X", request.get("method"))

addNewLine()
addIndent()
addWordsWithoutLeadingSpace( `"${request.get("url")}"`)

if ( headers && headers.size ) {
for( let p of request.get("headers").entries() ){
addNewLine()
addIndent()
let [ h,v ] = p
curlified.push( "-H " )
curlified.push( `"${h}: ${v.replace(/\$/g, "\\$")}"` )
addWordsWithoutLeadingSpace("-H", `'${h.replace(/'/g, "'\\''")}: ${v.replace(/'/g, "'\\''")}'`)
isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(h) && /^multipart\/form-data$/i.test(v)
}
}

if ( request.get("body") ){
if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
for( let [ k,v ] of request.get("body").entrySeq()) {
let extractedKey = extractKey(k)
curlified.push( "-F" )
let extractedKey = extractKey(k).replace(/'/g, "'\\''")
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-F")
if (v instanceof win.File) {
curlified.push(`"${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
addWords(`'${extractedKey}=@${v.name.replace(/'/g, "'\\''")}${v.type ? `;type=${v.type.replace(/'/g, "'\\''")}` : ""}'`)
} else {
curlified.push(`"${extractedKey}=${v}"` )
addWords(`'${extractedKey}=${v.replace(/'/g, "'\\''")}'`)
}
}
} else {
curlified.push( "-d" )
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-d")
let reqBody = request.get("body")
if (!Map.isMap(reqBody)) {
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "").replace(/\$/g, "\\$") )
if(typeof reqBody !== "string") {
reqBody = JSON.stringify(reqBody)
}
addWords(`'${reqBody.replace(/'/g, "'\\''")}'`)
} else {
let curlifyToJoin = []
for (let [k, v] of request.get("body").entrySeq()) {
let extractedKey = extractKey(k)
if (v instanceof win.File) {
curlifyToJoin.push(`"${extractedKey}":{"name":"${v.name}"${v.type ? `,"type":"${v.type}"` : ""}}`)
curlifyToJoin.push(` "${extractedKey}": {\n "name": "${v.name}"${v.type ? `,\n "type": "${v.type}"` : ""}\n }`)
} else {
curlifyToJoin.push(`"${extractedKey}":${JSON.stringify(v).replace(/\\n/g, "").replace("$", "\\$")}`)
curlifyToJoin.push(` "${extractedKey}": ${JSON.stringify(v, null, 2).replace(/(\r\n|\r|\n)/g, "\n ")}`)
}
}
curlified.push(`{${curlifyToJoin.join()}}`)
addWords(`'{\n${curlifyToJoin.join(",\n").replace(/'/g, "'\\''")}\n}'`)
}
}
} else if(!request.get("body") && request.get("method") === "POST") {
curlified.push( "-d" )
curlified.push( "\"\"" )
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-d", "\"\"")
}

return curlified.join( " " )
return curlified
}
25 changes: 24 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ export default function SwaggerUI(opts) {
showExtensions: false,
showCommonExtensions: false,
withCredentials: undefined,
requestSnippets: {
generators: {
"curl_bash": {
title: "cURL (bash)",
syntax: "bash"
},
"curl_powershell": {
title: "cURL (PowerShell)",
syntax: "powershell"
},
"curl_cmd": {
title: "cURL (CMD)",
syntax: "bash"
},
"node_native": {
title: "Node.js (Native)",
syntax: "javascript"
},
},
defaultExpanded: true,
languagesMask: null, // e.g. only show curl bash = ["curl_bash"]
},
supportedSubmitMethods: [
"get",
"put",
Expand Down Expand Up @@ -107,7 +129,8 @@ export default function SwaggerUI(opts) {
spec: {
spec: "",
url: constructorConfig.url
}
},
requestSnippets: constructorConfig.requestSnippets
}, constructorConfig.initialState)
}

Expand Down
219 changes: 219 additions & 0 deletions src/core/plugins/request-snippets/fn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import win from "../../window"
import { Map } from "immutable"
import Url from "url-parse"

/**
* if duplicate key name existed from FormData entries,
* we mutated the key name by appending a hashIdx
* @param {String} k - possibly mutated key name
* @return {String} - src key name
*/
const extractKey = (k) => {
const hashIdx = "_**[]"
if (k.indexOf(hashIdx) < 0) {
return k
}
return k.split(hashIdx)[0].trim()
}

const escapeShell = (str) => {
if (str === "-d ") {
return str
}
// eslint-disable-next-line no-useless-escape
if (!/^[A-Za-z0-9_\/-]+$/.test(str))
return ("'" + str
.replace(/'/g, "'\"'\"'") + "'")
.replace(/''/g, "")
else
return str
}

const escapeCMD = (str) => {
str = str
.replace(/\^/g, "^^")
.replace(/\\"/g, "\\\\\"")
.replace(/"/g, "\"\"")
.replace(/\n/g, "^\n")
if (str === "-d ") {
return str
.replace(/-d /g, "-d ^\n")
}
// eslint-disable-next-line no-useless-escape
if (!/^[A-Za-z0-9_\/-]+$/.test(str))
return "\"" + str + "\""
else
return str
}

const escapePowershell = (str) => {
if (str === "-d ") {
return str
}
if (/\n/.test(str)) {
return "@\"\n" + str.replace(/"/g, "\\\"").replace(/`/g, "``").replace(/\$/, "`$") + "\n\"@"
}
// eslint-disable-next-line no-useless-escape
if (!/^[A-Za-z0-9_\/-]+$/.test(str))
return "'" + str
.replace(/"/g, "\"\"")
.replace(/'/g, "''") + "'"
else
return str
}

function getStringBodyOfMap(request) {
let curlifyToJoin = []
for (let [k, v] of request.get("body").entrySeq()) {
let extractedKey = extractKey(k)
if (v instanceof win.File) {
curlifyToJoin.push(` "${extractedKey}": {\n "name": "${v.name}"${v.type ? `,\n "type": "${v.type}"` : ""}\n }`)
} else {
curlifyToJoin.push(` "${extractedKey}": ${JSON.stringify(v, null, 2).replace(/(\r\n|\r|\n)/g, "\n ")}`)
}
}
return `{\n${curlifyToJoin.join(",\n")}\n}`
}

const curlify = (request, escape, newLine, ext = "") => {
let isMultipartFormDataRequest = false
let curlified = ""
const addWords = (...args) => curlified += " " + args.map(escape).join(" ")
const addWordsWithoutLeadingSpace = (...args) => curlified += args.map(escape).join(" ")
const addNewLine = () => curlified += ` ${newLine}`
const addIndent = (level = 1) => curlified += " ".repeat(level)
let headers = request.get("headers")
curlified += "curl" + ext

if (request.get("curlOptions")) {
addWords()
}

addWords("-X", request.get("method"))

addNewLine()
addIndent()
addWordsWithoutLeadingSpace(`${request.get("url")}`)

if (headers && headers.size) {
for (let p of request.get("headers").entries()) {
addNewLine()
addIndent()
let [h, v] = p
addWordsWithoutLeadingSpace("-H", `${h}: ${v}`)
isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(h) && /^multipart\/form-data$/i.test(v)
}
}

if (request.get("body")) {
if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
for (let [k, v] of request.get("body").entrySeq()) {
let extractedKey = extractKey(k)
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-F")
if (v instanceof win.File) {
addWords(`${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : ""}`)
} else {
addWords(`${extractedKey}=${v}`)
}
}
} else {
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-d ")
let reqBody = request.get("body")
if (!Map.isMap(reqBody)) {
if (typeof reqBody !== "string") {
reqBody = JSON.stringify(reqBody)
}
addWordsWithoutLeadingSpace(reqBody)
} else {
addWordsWithoutLeadingSpace(getStringBodyOfMap(request))
}
}
} else if (!request.get("body") && request.get("method") === "POST") {
addNewLine()
addIndent()
addWordsWithoutLeadingSpace("-d ")
addWordsWithoutLeadingSpace("")
}

return curlified
}

// eslint-disable-next-line camelcase
export const requestSnippetGenerator_curl_powershell = (request) => {
return curlify(request, escapePowershell, "`\n", ".exe")
}

// eslint-disable-next-line camelcase
export const requestSnippetGenerator_curl_bash = (request) => {
return curlify(request, escapeShell, "\\\n")
}

// eslint-disable-next-line camelcase
export const requestSnippetGenerator_curl_cmd = (request) => {
return curlify(request, escapeCMD, "^\n")
}

// eslint-disable-next-line camelcase
export const requestSnippetGenerator_node_native = (request) => {
const url = new Url(request.get("url"))
let isMultipartFormDataRequest = false
const headers = request.get("headers")
if(headers && headers.size) {
request.get("headers").map((val, key) => {
isMultipartFormDataRequest = isMultipartFormDataRequest || /^content-type$/i.test(key) && /^multipart\/form-data$/i.test(val)
})
}
const packageStr = url.protocol === "https:" ? "https" : "http"
let reqBody = request.get("body")
if (request.get("body")) {
if (isMultipartFormDataRequest && ["POST", "PUT", "PATCH"].includes(request.get("method"))) {
return "throw new Error(\"Currently unsupported content-type: /^multipart\\/form-data$/i\");"
} else {
if (!Map.isMap(reqBody)) {
if (typeof reqBody !== "string") {
reqBody = JSON.stringify(reqBody)
}
} else {
reqBody = getStringBodyOfMap(request)
}
}
} else if (!request.get("body") && request.get("method") === "POST") {
reqBody = ""
}

const stringBody = "`" + (reqBody || "")
.replace(/\\n/g, "\n")
.replace(/`/g, "\\`")
+ "`"

return `const http = require("${packageStr}");
const options = {
"method": "${request.get("method")}",
"hostname": "${url.host}",
"port": ${url.port || "null"},
"path": "${url.pathname}"${headers && headers.size ? `,
"headers": {
${request.get("headers").map((val, key) => `"${key}": "${val}"`).valueSeq().join(",\n ")}
}` : ""}
};
const req = http.request(options, function (res) {
const chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function () {
const body = Buffer.concat(chunks);
console.log(body.toString());
});
});
${reqBody ? `\nreq.write(${stringBody});` : ""}
req.end();`
}
16 changes: 16 additions & 0 deletions src/core/plugins/request-snippets/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as fn from "./fn"
import * as selectors from "./selectors"
import { RequestSnippets } from "./request-snippets"
export default () => {
return {
components: {
RequestSnippets
},
fn,
statePlugins: {
requestSnippets: {
selectors
}
}
}
}
Loading

0 comments on commit 5d4c05a

Please sign in to comment.