feat: request snippets plugin (#6910)

This commit is contained in:
Mahtis Michel
2021-03-10 20:02:34 +01:00
committed by GitHub
parent 15b8c0c929
commit 8405fa0101
18 changed files with 537 additions and 210 deletions

View File

@@ -65,6 +65,8 @@ Parameter name | Docker variable | Description
<a name="syntaxHighlight.activate"></a>`syntaxHighlight.activate` | _Unavailable_ | `Boolean=true`. Whether syntax highlighting should be activated or not. <a name="syntaxHighlight.activate"></a>`syntaxHighlight.activate` | _Unavailable_ | `Boolean=true`. Whether syntax highlighting should be activated or not.
<a name="syntaxHighlight.theme"></a>`syntaxHighlight.theme` | _Unavailable_ | `String=["agate"*, "arta", "monokai", "nord", "obsidian", "tomorrow-night"]`. [Highlight.js](https://highlightjs.org/static/demo/) syntax coloring theme to use. (Only these 6 styles are available.) <a name="syntaxHighlight.theme"></a>`syntaxHighlight.theme` | _Unavailable_ | `String=["agate"*, "arta", "monokai", "nord", "obsidian", "tomorrow-night"]`. [Highlight.js](https://highlightjs.org/static/demo/) syntax coloring theme to use. (Only these 6 styles are available.)
<a name="tryItOutEnabled"></a>`tryItOutEnabled` | `TRY_IT_OUT_ENABLED` | `Boolean=false`. Controls whether the "Try it out" section should be enabled by default. <a name="tryItOutEnabled"></a>`tryItOutEnabled` | `TRY_IT_OUT_ENABLED` | `Boolean=false`. Controls whether the "Try it out" section should be enabled by default.
<a name="requestSnippets"></a>`requestSnippets` | _Unavailable_ | `Object`. This is the default configuration section for the the requestSnippets plugin.<br>requestSnippets: {<br>&nbsp;&nbsp;generators: {<br>&nbsp;&nbsp;&nbsp;&nbsp;"curl_bash": {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: "cURL (bash)",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;syntax: "bash"<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;"curl_powershell": {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: "cURL (PowerShell)",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;syntax: "powershell"<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;"curl_cmd": {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: "cURL (CMD)",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;syntax: "bash"<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;"node_native": {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title: "Node.js (Native)",<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;syntax: "javascript"<br>&nbsp;&nbsp;&nbsp;&nbsp;},<br>&nbsp;&nbsp;},<br>&nbsp;&nbsp;defaultExpanded: true,<br>&nbsp;&nbsp;languagesMask: null, // e.g. only show curl bash = \["curl_bash"\]<br>},
##### Network ##### Network
@@ -168,4 +170,4 @@ SPEC="{ \"openapi\": \"3.0.0\" }"
```sh ```sh
SUPPORTED_SUBMIT_METHODS=['get', 'post'] SUPPORTED_SUBMIT_METHODS=['get', 'post']
URLS=[ { url: 'http://petstore.swagger.io/v2/swagger.json', name: 'Petstore' } ] URLS=[ { url: 'http://petstore.swagger.io/v2/swagger.json', name: 'Petstore' } ]
``` ```

View File

@@ -1,9 +1,9 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import curlify from "core/curlify"
import { CopyToClipboard } from "react-copy-to-clipboard" import { CopyToClipboard } from "react-copy-to-clipboard"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting" import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get" import get from "lodash/get"
import { requestSnippetGenerator_curl_bash } from "../plugins/request-snippets/fn"
export default class Curl extends React.Component { export default class Curl extends React.Component {
static propTypes = { static propTypes = {
@@ -13,7 +13,7 @@ export default class Curl extends React.Component {
render() { render() {
let { request, getConfigs } = this.props let { request, getConfigs } = this.props
let curl = curlify(request) let curl = requestSnippetGenerator_curl_bash(request)
const config = getConfigs() const config = getConfigs()

View File

@@ -49,7 +49,7 @@ export default class LiveResponse extends React.Component {
render() { render() {
const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props
const { showMutatedRequest } = getConfigs() const { showMutatedRequest, requestSnippetsEnabled } = getConfigs()
const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method) const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method)
const status = response.get("status") const status = response.get("status")
@@ -62,7 +62,6 @@ export default class LiveResponse extends React.Component {
const headersKeys = Object.keys(headers) const headersKeys = Object.keys(headers)
const contentType = headers["content-type"] || headers["Content-Type"] const contentType = headers["content-type"] || headers["Content-Type"]
const Curl = getComponent("curl")
const ResponseBody = getComponent("responseBody") const ResponseBody = getComponent("responseBody")
const returnObject = headersKeys.map(key => { const returnObject = headersKeys.map(key => {
var joinedHeaders = Array.isArray(headers[key]) ? headers[key].join() : headers[key] var joinedHeaders = Array.isArray(headers[key]) ? headers[key].join() : headers[key]
@@ -70,10 +69,14 @@ export default class LiveResponse extends React.Component {
}) })
const hasHeaders = returnObject.length !== 0 const hasHeaders = returnObject.length !== 0
const Markdown = getComponent("Markdown", true) const Markdown = getComponent("Markdown", true)
const RequestSnippets = getComponent("RequestSnippets", true)
const Curl = getComponent("curl")
return ( return (
<div> <div>
{ curlRequest && <Curl request={ curlRequest } getConfigs={ getConfigs } /> } { curlRequest && (requestSnippetsEnabled === true || requestSnippetsEnabled === "true"
? <RequestSnippets request={ curlRequest }/>
: <Curl request={ curlRequest } getConfigs={ getConfigs } />) }
{ url && <div> { url && <div>
<h4>Request URL</h4> <h4>Request URL</h4>
<div className="request-url"> <div className="request-url">

View File

@@ -1,75 +0,0 @@
import win from "./window"
import { Map } from "immutable"
/**
* 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()
}
export default function curl( request ){
let curlified = []
let isMultipartFormDataRequest = false
let headers = request.get("headers")
curlified.push( "curl" )
if (request.get("curlOptions")) {
curlified.push(...request.get("curlOptions"))
}
curlified.push( "-X", request.get("method") )
curlified.push( `"${request.get("url")}"`)
if ( headers && headers.size ) {
for( let p of request.get("headers").entries() ){
let [ h,v ] = p
curlified.push( "-H " )
curlified.push( `"${h}: ${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" )
if (v instanceof win.File) {
curlified.push(`"${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
} else {
curlified.push(`"${extractedKey}=${v}"` )
}
}
} else {
curlified.push( "-d" )
let reqBody = request.get("body")
if (!Map.isMap(reqBody)) {
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "").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}"` : ""}}`)
} else {
curlifyToJoin.push(`"${extractedKey}":${JSON.stringify(v).replace(/\\n/g, "").replace("$", "\\$")}`)
}
}
curlified.push(`{${curlifyToJoin.join()}}`)
}
}
} else if(!request.get("body") && request.get("method") === "POST") {
curlified.push( "-d" )
curlified.push( "\"\"" )
}
return curlified.join( " " )
}

View File

@@ -53,6 +53,29 @@ export default function SwaggerUI(opts) {
showExtensions: false, showExtensions: false,
showCommonExtensions: false, showCommonExtensions: false,
withCredentials: undefined, withCredentials: undefined,
requestSnippetsEnabled: false,
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: [ supportedSubmitMethods: [
"get", "get",
"put", "put",
@@ -107,7 +130,8 @@ export default function SwaggerUI(opts) {
spec: { spec: {
spec: "", spec: "",
url: constructorConfig.url url: constructorConfig.url
} },
requestSnippets: constructorConfig.requestSnippets
}, constructorConfig.initialState) }, constructorConfig.initialState)
} }

View File

@@ -0,0 +1,217 @@
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 (!/^[_\/-]/g.test(str))
return ("'" + str
.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 (!/^[_\/-]/g.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 (!/^[_\/-]/g.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.has("curlOptions")) {
addWords(...request.get("curlOptions"))
}
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 ''")
}
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();`
}

View File

@@ -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
}
}
}
}

View File

@@ -0,0 +1,127 @@
import React from "react"
import { CopyToClipboard } from "react-copy-to-clipboard"
import PropTypes from "prop-types"
import get from "lodash/get"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
export class RequestSnippets extends React.Component {
constructor() {
super()
this.state = {
activeLanguage: this.props?.requestSnippetsSelectors?.getSnippetGenerators()?.keySeq().first(),
expanded: this.props?.requestSnippetsSelectors?.getDefaultExpanded(),
}
}
static propTypes = {
request: PropTypes.object.isRequired,
requestSnippetsSelectors: PropTypes.object.isRequired,
getConfigs: PropTypes.object.isRequired,
requestSnippetsActions: PropTypes.object.isRequired,
}
render() {
const {request, getConfigs, requestSnippetsSelectors } = this.props
const snippetGenerators = requestSnippetsSelectors.getSnippetGenerators()
const activeLanguage = this.state.activeLanguage || snippetGenerators.keySeq().first()
const activeGenerator = snippetGenerators.get(activeLanguage)
const snippet = activeGenerator.get("fn")(request)
const onGenChange = (key) => {
const needsChange = activeLanguage !== key
if(needsChange) {
this.setState({
activeLanguage: key
})
}
}
const style = {
cursor: "pointer",
lineHeight: 1,
display: "inline-flex",
backgroundColor: "rgb(250, 250, 250)",
paddingBottom: "0",
paddingTop: "0",
border: "1px solid rgb(51, 51, 51)",
borderRadius: "4px 4px 0 0",
boxShadow: "none",
borderBottom: "none"
}
const activeStyle = {
cursor: "pointer",
lineHeight: 1,
display: "inline-flex",
backgroundColor: "rgb(51, 51, 51)",
boxShadow: "none",
border: "1px solid rgb(51, 51, 51)",
paddingBottom: "0",
paddingTop: "0",
borderRadius: "4px 4px 0 0",
marginTop: "-5px",
marginRight: "-5px",
marginLeft: "-5px",
zIndex: "9999",
borderBottom: "none"
}
const getBtnStyle = (key) => {
if (key === activeLanguage) {
return activeStyle
}
return style
}
const config = getConfigs()
const SnippetComponent = config?.syntaxHighlight?.activated
? <SyntaxHighlighter
language={activeGenerator.get("syntax")}
className="curl microlight"
onWheel={function(e) {return this.preventYScrollingBeyondElement(e)}}
style={getStyle(get(config, "syntaxHighlight.theme"))}
>
{snippet}
</SyntaxHighlighter>
:
<textarea readOnly={true} className="curl" value={snippet}></textarea>
const expanded = this.state.expanded === undefined ? this.props?.requestSnippetsSelectors?.getDefaultExpanded() : this.state.expanded
return (
<div>
<div style={{width: "100%", display: "flex", justifyContent: "flex-start", alignItems: "center", marginBottom: "15px"}}>
<h4
style={{ cursor: "pointer" }}
onClick={() => this.setState({expanded: !expanded})}
>Snippets</h4>
<button
onClick={() => this.setState({expanded: !expanded})}
style={{ border: "none", background: "none" }}
title={expanded ? "Collapse operation": "Expand operation"}
>
<svg className="arrow" width="10" height="10">
<use href={expanded ? "#large-arrow-down" : "#large-arrow"} xlinkHref={expanded ? "#large-arrow-down" : "#large-arrow"} />
</svg>
</button>
</div>
{
expanded && <div className="curl-command">
<div style={{paddingLeft: "15px", paddingRight: "10px", width: "100%", display: "flex"}}>
{
snippetGenerators.map((gen, key) => {
return (<div style={getBtnStyle(key)} className="btn" key={key} onClick={() => onGenChange(key)}>
<h4 style={key === activeLanguage ? {color: "white",} : {}}>{gen.get("title")}</h4>
</div>)
})
}
</div>
<div className="copy-to-clipboard">
<CopyToClipboard text={snippet}>
<button />
</CopyToClipboard>
</div>
<div>
{SnippetComponent}
</div>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,45 @@
import { createSelector } from "reselect"
import { Map } from "immutable"
const state = state => state || Map()
export const getGenerators = createSelector(
state,
state => {
const languageKeys = state
.get("languages")
const generators = state
.get("generators", Map())
if(!languageKeys) {
return generators
}
return generators
.filter((v, key) => languageKeys.includes(key))
}
)
export const getSnippetGenerators = (state) => ({ fn }) => {
const getGenFn = (key) => fn[`requestSnippetGenerator_${key}`]
return getGenerators(state)
.map((gen, key) => {
const genFn = getGenFn(key)
if(typeof genFn !== "function") {
return null
}
return gen.set("fn", genFn)
})
.filter(v => v)
}
export const getActiveLanguage = createSelector(
state,
state => state
.get("activeLanguage")
)
export const getDefaultExpanded = createSelector(
state,
state => state
.get("defaultExpanded")
)

View File

@@ -5,7 +5,7 @@ import serializeError from "serialize-error"
import isString from "lodash/isString" import isString from "lodash/isString"
import debounce from "lodash/debounce" import debounce from "lodash/debounce"
import set from "lodash/set" import set from "lodash/set"
import { isJSONObject, paramToValue, isEmptyValue } from "core/utils" import { paramToValue, isEmptyValue } from "core/utils"
// Actions conform to FSA (flux-standard-actions) // Actions conform to FSA (flux-standard-actions)
// {type: string,payload: Any|Error, meta: obj, error: bool} // {type: string,payload: Any|Error, meta: obj, error: bool}
@@ -426,9 +426,7 @@ export const executeRequest = (req) =>
const requestBody = oas3Selectors.requestBodyValue(pathName, method) const requestBody = oas3Selectors.requestBodyValue(pathName, method)
const requestBodyInclusionSetting = oas3Selectors.requestBodyInclusionSetting(pathName, method) const requestBodyInclusionSetting = oas3Selectors.requestBodyInclusionSetting(pathName, method)
if(isJSONObject(requestBody)) { if(requestBody && requestBody.toJS) {
req.requestBody = JSON.parse(requestBody)
} else if(requestBody && requestBody.toJS) {
req.requestBody = requestBody req.requestBody = requestBody
.map( .map(
(val) => { (val) => {
@@ -445,7 +443,7 @@ export const executeRequest = (req) =>
) || requestBodyInclusionSetting.get(key) ) || requestBodyInclusionSetting.get(key)
) )
.toJS() .toJS()
} else{ } else {
req.requestBody = requestBody req.requestBody = requestBody
} }
} }

View File

@@ -3,6 +3,7 @@ import layout from "core/plugins/layout"
import spec from "core/plugins/spec" import spec from "core/plugins/spec"
import view from "core/plugins/view" import view from "core/plugins/view"
import samples from "core/plugins/samples" import samples from "core/plugins/samples"
import requestSnippets from "core/plugins/request-snippets"
import logs from "core/plugins/logs" import logs from "core/plugins/logs"
import swaggerJs from "core/plugins/swagger-js" import swaggerJs from "core/plugins/swagger-js"
import auth from "core/plugins/auth" import auth from "core/plugins/auth"
@@ -191,6 +192,7 @@ export default function() {
downloadUrlPlugin, downloadUrlPlugin,
deepLinkingPlugin, deepLinkingPlugin,
filter, filter,
onComplete onComplete,
requestSnippets
] ]
} }

View File

@@ -6,6 +6,8 @@ import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml"
import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash" import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash"
import yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml" import yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml"
import http from "react-syntax-highlighter/dist/esm/languages/hljs/http" import http from "react-syntax-highlighter/dist/esm/languages/hljs/http"
import powershell from "react-syntax-highlighter/dist/esm/languages/hljs/powershell"
import javascript from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"
import agate from "react-syntax-highlighter/dist/esm/styles/hljs/agate" import agate from "react-syntax-highlighter/dist/esm/styles/hljs/agate"
import arta from "react-syntax-highlighter/dist/esm/styles/hljs/arta" import arta from "react-syntax-highlighter/dist/esm/styles/hljs/arta"
@@ -20,6 +22,8 @@ SyntaxHighlighter.registerLanguage("xml", xml)
SyntaxHighlighter.registerLanguage("yaml", yaml) SyntaxHighlighter.registerLanguage("yaml", yaml)
SyntaxHighlighter.registerLanguage("http", http) SyntaxHighlighter.registerLanguage("http", http)
SyntaxHighlighter.registerLanguage("bash", bash) SyntaxHighlighter.registerLanguage("bash", bash)
SyntaxHighlighter.registerLanguage("powershell", powershell)
SyntaxHighlighter.registerLanguage("javascript", javascript)
const styles = {agate, arta, monokai, nord, obsidian, "tomorrow-night": tomorrowNight} const styles = {agate, arta, monokai, nord, obsidian, "tomorrow-night": tomorrowNight}
export const availableStyles = Object.keys(styles) export const availableStyles = Object.keys(styles)

View File

@@ -32,25 +32,6 @@ const DEFAULT_RESPONSE_KEY = "default"
export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe) export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe)
export function isJSONObject (str) {
try {
var o = JSON.parse(str)
// Handle non-exception-throwing cases:
// Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
// but... JSON.parse(null) returns null, and typeof null === "object",
// so we must check for that, too. Thankfully, null is falsey, so this suffices:
if (o && typeof o === "object") {
return o
}
}
catch (e) {
// do nothing
}
return false
}
export function objectify (thing) { export function objectify (thing) {
if(!isObject(thing)) if(!isObject(thing))
return {} return {}

View File

@@ -208,7 +208,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body // Assert on the curl body
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`) .contains(`-d '${exampleA.serializedValue || exampleA.value}'`)
}) })
it("should set default static and Try-It-Out values based on choosing the second member in static mode", () => { it("should set default static and Try-It-Out values based on choosing the second member in static mode", () => {
@@ -234,7 +234,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL // Assert on the request URL
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`) .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
}) })
it("should set default static and Try-It-Out values based on choosing the second member in Try-It-Out mode", () => { it("should set default static and Try-It-Out values based on choosing the second member in Try-It-Out mode", () => {
@@ -257,7 +257,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL // Assert on the request URL
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`) .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Switch to static docs // Switch to static docs
.get(".try-out__btn") .get(".try-out__btn")
.click() .click()
@@ -323,7 +323,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL // Assert on the request URL
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`) .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
}) })
it("should use the first example for the media type when changing the media type without prior interactions with the value", () => { it("should use the first example for the media type when changing the media type without prior interactions with the value", () => {
@@ -349,7 +349,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL // Assert on the request URL
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`) .contains(`-d '${exampleA.serializedValue || exampleA.value}'`)
}) })
it("static mode toggling: mediaType -> example -> mediaType -> example", () => { it("static mode toggling: mediaType -> example -> mediaType -> example", () => {
@@ -489,7 +489,7 @@ function RequestBodyPrimitiveTestCases({
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains( .contains(
`-d "${customUserInputExpectedCurlSubstring || customUserInput}"` `-d '${customUserInputExpectedCurlSubstring || customUserInput}'`
) )
// Choose exampleB // Choose exampleB
@@ -508,7 +508,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body // Assert on the curl body
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`) .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Ensure the modified value is still accessible // Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select") .get(".opblock-section-request-body .examples-select > select")
@@ -530,7 +530,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body // Assert on the curl body
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleB.serializedValue || exampleB.value}"`) .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Ensure the modified value is still accessible // Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select") .get(".opblock-section-request-body .examples-select > select")
@@ -552,7 +552,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body // Assert on the curl body
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains(`-d "${exampleA.serializedValue || exampleA.value}"`) .contains(`-d '${exampleA.serializedValue || exampleA.value}'`)
// Ensure the modified value is still the same value // Ensure the modified value is still the same value
.get(".opblock-section-request-body .examples-select > select") .get(".opblock-section-request-body .examples-select > select")
@@ -571,7 +571,7 @@ function RequestBodyPrimitiveTestCases({
// TODO: use an interceptor instead of curl // TODO: use an interceptor instead of curl
.get(".curl") .get(".curl")
.contains( .contains(
`-d "${customUserInputExpectedCurlSubstring || customUserInput}"` `-d '${customUserInputExpectedCurlSubstring || customUserInput}'`
) )
}) })

View File

@@ -2,7 +2,7 @@
* @prettier * @prettier
*/ */
const { const {
ParameterPrimitiveTestCases, ParameterPrimitiveTestCases,
RequestBodyPrimitiveTestCases, RequestBodyPrimitiveTestCases,
ResponsePrimitiveTestCases, ResponsePrimitiveTestCases,
@@ -256,7 +256,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.get("#operations-default-post_Array") .get("#operations-default-post_Array")
.click() .click()
.get(".json-schema-form-item > input") .get(".json-schema-form-item > input")
.then(inputs => { .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"a", "a",
"b", "b",
@@ -276,7 +276,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.get(".parameters-col_description .examples-select > select") .get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB") .select("ArrayExampleB")
.get(".json-schema-form-item > input") .get(".json-schema-form-item > input")
.then(inputs => { .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1", "1",
"2", "2",
@@ -317,7 +317,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.type("5") .type("5")
// Assert against the input fields // Assert against the input fields
.get(".json-schema-form-item > input") .get(".json-schema-form-item > input")
.then(inputs => { .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1", "1",
"2", "2",
@@ -351,7 +351,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.select("ArrayExampleB") .select("ArrayExampleB")
// Assert against the input fields // Assert against the input fields
.get(".json-schema-form-item > input") .get(".json-schema-form-item > input")
.then(inputs => { .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1", "1",
"2", "2",
@@ -367,7 +367,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.select("__MODIFIED__VALUE__") .select("__MODIFIED__VALUE__")
// Assert that our modified value is back // Assert that our modified value is back
.get(".json-schema-form-item > input") .get(".json-schema-form-item > input")
.then(inputs => { .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([ expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1", "1",
"2", "2",
@@ -566,41 +566,43 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
}) })
}) })
describe("in a Request Body", () => { describe("in a Request Body", () => {
const exampleA = JSON.stringify(
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
)
const exampleB = JSON.stringify(
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
)
RequestBodyPrimitiveTestCases({ RequestBodyPrimitiveTestCases({
operationDomId: "#operations-default-post_Object", operationDomId: "#operations-default-post_Object",
primaryMediaType: "application/json", primaryMediaType: "application/json",
// ↓ not a typo, Cypress requires escaping { when using `cy.type` // ↓ not a typo, Cypress requires escaping { when using `cy.type`
customUserInput: `{{} "openapiIsCool": true }`, customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true", customExpectedUrlSubstring: "?openapiIsCool=true",
customUserInputExpectedCurlSubstring: `{\\"openapiIsCool\\":true}`, customUserInputExpectedCurlSubstring: `{ "openapiIsCool": true }`,
exampleA: { exampleA: {
key: "ObjectExampleA", key: "ObjectExampleA",
serializedValue: `{\\"firstName\\":\\"Kyle\\",\\"lastName\\":\\"Shockey\\",\\"email\\":\\"kyle.shockey@smartbear.com\\"}`, serializedValue: exampleA,
value: JSON.stringify( value: exampleA,
{
firstName: "Kyle",
lastName: "Shockey",
email: "kyle.shockey@smartbear.com",
},
null,
2
),
summary: "A user's contact info", summary: "A user's contact info",
}, },
exampleB: { exampleB: {
key: "ObjectExampleB", key: "ObjectExampleB",
serializedValue: `{\\"name\\":\\"Abbey\\",\\"type\\":\\"kitten\\",\\"color\\":\\"calico\\",\\"gender\\":\\"female\\",\\"age\\":\\"11 weeks\\"}`, serializedValue: exampleB,
value: JSON.stringify( value: exampleB,
{
name: "Abbey",
type: "kitten",
color: "calico",
gender: "female",
age: "11 weeks",
},
null,
2
),
summary: "A wonderful kitten's info", summary: "A wonderful kitten's info",
}, },
}) })

View File

@@ -95,7 +95,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".responses-wrapper .curl-command") .get(".responses-wrapper .curl-command")
.should("exist") .should("exist")
.get(".responses-wrapper .curl-command span") .get(".responses-wrapper .curl-command span")
.should("contains.text", "\" \"") .should("contains.text", "' '")
}) })
}) })

View File

@@ -3,9 +3,9 @@ import React from "react"
import { fromJSOrdered } from "core/utils" import { fromJSOrdered } from "core/utils"
import expect, { createSpy } from "expect" import expect, { createSpy } from "expect"
import { shallow } from "enzyme" import { shallow } from "enzyme"
import Curl from "components/curl"
import LiveResponse from "components/live-response" import LiveResponse from "components/live-response"
import ResponseBody from "components/response-body" import ResponseBody from "components/response-body"
import { RequestSnippets } from "core/plugins/request-snippets/request-snippets"
describe("<LiveResponse/>", function () { describe("<LiveResponse/>", function () {
let request = fromJSOrdered({ let request = fromJSOrdered({
@@ -36,7 +36,7 @@ describe("<LiveResponse/>", function () {
] ]
tests.forEach(function (test) { tests.forEach(function (test) {
it("passes " + test.expected.request + " to Curl when showMutatedRequest = " + test.showMutatedRequest, function () { it("passes " + test.expected.request + " to RequestSnippets when showMutatedRequest = " + test.showMutatedRequest, function () {
// Given // Given
@@ -54,7 +54,7 @@ describe("<LiveResponse/>", function () {
let requestForSpy = createSpy().andReturn(request) let requestForSpy = createSpy().andReturn(request)
let components = { let components = {
curl: Curl, RequestSnippets: RequestSnippets,
responseBody: ResponseBody responseBody: ResponseBody
} }
@@ -69,7 +69,7 @@ describe("<LiveResponse/>", function () {
return components[c] return components[c]
}, },
displayRequestDuration: true, displayRequestDuration: true,
getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest }) getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest, requestSnippetsEnabled: true })
} }
// When // When
@@ -79,9 +79,9 @@ describe("<LiveResponse/>", function () {
expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls) expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls)
expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls) expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls)
const curl = wrapper.find(Curl) const snippets = wrapper.find("RequestSnippets")
expect(curl.length).toEqual(1) expect(snippets.length).toEqual(1)
expect(curl.props().request).toBe(requests[test.expected.request]) expect(snippets.props().request).toBe(requests[test.expected.request])
const expectedUrl = requests[test.expected.request].get("url") const expectedUrl = requests[test.expected.request].get("url")
expect(wrapper.find("div.request-url pre.microlight").text()).toEqual(expectedUrl) expect(wrapper.find("div.request-url pre.microlight").text()).toEqual(expectedUrl)

View File

@@ -1,18 +1,19 @@
import Im from "immutable" import Im from "immutable"
import curl from "core/curlify" import { requestSnippetGenerator_curl_bash as curl } from "core/plugins/request-snippets/fn.js"
import win from "core/window" import win from "core/window"
describe("curlify", function () { describe("curlify", function () {
it("prints a curl statement with custom content-type", function () { it("prints a curl statement with custom content-type", function () {
const body = JSON.stringify({
id: 0,
name: "doggie",
status: "available"
}, null, 2)
let req = { let req = {
url: "http://example.com", url: "http://example.com",
method: "POST", method: "POST",
body: { body,
id: 0,
name: "doggie",
status: "available"
},
headers: { headers: {
Accept: "application/json", Accept: "application/json",
"content-type": "application/json" "content-type": "application/json"
@@ -21,7 +22,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"Accept: application/json\" -H \"content-type: application/json\" -d {\"id\":0,\"name\":\"doggie\",\"status\":\"available\"}") expect(curlified).toEqual(`curl -X 'POST' \\\n 'http://example.com' \\\n -H 'Accept: application/json' \\\n -H 'content-type: application/json' \\\n -d '${body}'`)
}) })
it("does add a empty data param if no request body given", function () { it("does add a empty data param if no request body given", function () {
@@ -32,7 +33,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -d \"\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -d ''")
}) })
it("does not change the case of header in curl", function () { it("does not change the case of header in curl", function () {
@@ -46,7 +47,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"conTenT Type: application/Moar\" -d \"\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'conTenT Type: application/Moar' \\\n -d ''")
}) })
it("prints a curl statement with an array of query params", function () { it("prints a curl statement with an array of query params", function () {
@@ -57,7 +58,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X GET \"http://swaggerhub.com/v1/one?name=john|smith\"") expect(curlified).toEqual("curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith'")
}) })
it("prints a curl statement with an array of query params and auth", function () { it("prints a curl statement with an array of query params and auth", function () {
@@ -71,24 +72,25 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X GET \"http://swaggerhub.com/v1/one?name=john|smith\" -H \"authorization: Basic Zm9vOmJhcg==\"") expect(curlified).toEqual("curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -H 'authorization: Basic Zm9vOmJhcg=='")
}) })
it("prints a curl statement with html", function () { it("prints a curl statement with html", function () {
const body = {
description: "<b>Test</b>"
}
let req = { let req = {
url: "http://swaggerhub.com/v1/one?name=john|smith", url: "http://swaggerhub.com/v1/one?name=john|smith",
method: "GET", method: "GET",
headers: { headers: {
accept: "application/json" accept: "application/json"
}, },
body: { body
description: "<b>Test</b>"
}
} }
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X GET \"http://swaggerhub.com/v1/one?name=john|smith\" -H \"accept: application/json\" -d {\"description\":\"<b>Test</b>\"}") expect(curlified).toEqual(`curl -X 'GET' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -H 'accept: application/json' \\\n -d '${JSON.stringify(body, null, 2)}'`)
}) })
it("handles post body with html", function () { it("handles post body with html", function () {
@@ -105,7 +107,12 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://swaggerhub.com/v1/one?name=john|smith\" -H \"accept: application/json\" -d {\"description\":\"<b>Test</b>\"}") expect(curlified).toEqual(`curl -X 'POST' \\
'http://swaggerhub.com/v1/one?name=john|smith' \\
-H 'accept: application/json' \\
-d '{
"description": "<b>Test</b>"
}'`)
}) })
it("handles post body with special chars", function () { it("handles post body with special chars", function () {
@@ -120,7 +127,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://swaggerhub.com/v1/one?name=john|smith\" -d {\"description\":\"@prefix nif:<http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#> .@prefix itsrdf: <http://www.w3.org/2005/11/its/rdf#> .\"}") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -d '{\n \"description\": \"@prefix nif:<http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#> .\\n@prefix itsrdf: <http://www.w3.org/2005/11/its/rdf#> .\"\n}'")
}) })
it("handles delete form with parameters", function () { it("handles delete form with parameters", function () {
@@ -134,7 +141,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X DELETE \"http://example.com\" -H \"accept: application/x-www-form-urlencoded\"") expect(curlified).toEqual("curl -X 'DELETE' \\\n 'http://example.com' \\\n -H 'accept: application/x-www-form-urlencoded'")
}) })
it("should print a curl with formData", function () { it("should print a curl with formData", function () {
@@ -150,7 +157,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"name=Sahar\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'name=Sahar'")
}) })
it("should print a curl with formData that extracts array representation with hashIdx", function () { it("should print a curl with formData that extracts array representation with hashIdx", function () {
@@ -170,7 +177,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"fruits[]=apple\" -F \"fruits[]=banana\" -F \"fruits[]=grape\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'fruits[]=apple' \\\n -F 'fruits[]=banana' \\\n -F 'fruits[]=grape'")
}) })
it("should print a curl with formData and file", function () { it("should print a curl with formData and file", function () {
@@ -190,7 +197,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"file=@file.txt;type=text/plain\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'")
}) })
it("should print a curl without form data type if type is unknown", function () { it("should print a curl without form data type if type is unknown", function () {
@@ -210,7 +217,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"file=@file.txt\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt'")
}) })
it("prints a curl post statement from an object", function () { it("prints a curl post statement from an object", function () {
@@ -227,7 +234,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"accept: application/json\" -d {\"id\":10101}") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'accept: application/json' \\\n -d '{\n \"id\": 10101\n}'")
}) })
it("prints a curl post statement from a string containing a single quote", function () { it("prints a curl post statement from a string containing a single quote", function () {
@@ -242,7 +249,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"accept: application/json\" -d \"{\\\"id\\\":\\\"foo'bar\\\"}\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'accept: application/json' \\\n -d '{\"id\":\"foo'\\''bar\"}'")
}) })
describe("given multiple entries with file", function () { describe("given multiple entries with file", function () {
@@ -267,7 +274,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"x-custom-name: multipart/form-data\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"file=@file.txt;type=text/plain\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -H 'content-type: multipart/form-data' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'")
}) })
}) })
@@ -292,7 +299,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"content-type: multipart/form-data\" -H \"x-custom-name: any-value\" -F \"id=123\" -F \"file=@file.txt;type=text/plain\"") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'content-type: multipart/form-data' \\\n -H 'x-custom-name: any-value' \\\n -F 'id=123' \\\n -F 'file=@file.txt;type=text/plain'")
}) })
}) })
}) })
@@ -315,7 +322,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"x-custom-name: multipart/form-data\" -d {\"id\":\"123\",\"file\":{\"name\":\"file.txt\",\"type\":\"text/plain\"}}") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -d '{\n \"id\": \"123\",\n \"file\": {\n \"name\": \"file.txt\",\n \"type\": \"text/plain\"\n }\n}'")
}) })
it("shoud print a proper curl as -d <data>, no file type provided", function () { it("shoud print a proper curl as -d <data>, no file type provided", function () {
@@ -335,36 +342,10 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"x-custom-name: multipart/form-data\" -d {\"id\":\"123\",\"file\":{\"name\":\"file.txt\"}}") expect(curlified).toEqual("curl -X 'POST' \\\n 'http://example.com' \\\n -H 'x-custom-name: multipart/form-data' \\\n -d '{\n \"id\": \"123\",\n \"file\": {\n \"name\": \"file.txt\"\n }\n}'")
}) })
}) })
it("should escape dollar signs in headers and request body", function () {
let req = {
url: "http://example.com",
method: "POST",
headers: { "X-DOLLAR": "token/123$" },
body: "CREATE ($props)"
}
let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -H \"X-DOLLAR: token/123\\$\" -d \"CREATE (\\$props)\"")
})
it("should escape multiple dollar signs", function () {
let req = {
url: "http://example.com",
method: "POST",
headers: { },
body: "RETURN $x + $y"
}
let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -X POST \"http://example.com\" -d \"RETURN \\$x + \\$y\"")
})
it("should include curlOptions from the request in the curl command", function () { it("should include curlOptions from the request in the curl command", function () {
let req = { let req = {
url: "http://example.com", url: "http://example.com",
@@ -375,7 +356,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -g -X GET \"http://example.com\" -H \"X-DOLLAR: token/123\\$\"") expect(curlified).toEqual("curl -g -X 'GET' \\\n 'http://example.com' \\\n -H 'X-DOLLAR: token/123$'")
}) })
it("should include multiple curlOptions from the request in the curl command", function () { it("should include multiple curlOptions from the request in the curl command", function () {
@@ -388,6 +369,6 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req)) let curlified = curl(Im.fromJS(req))
expect(curlified).toEqual("curl -g --limit-rate 20k -X GET \"http://example.com\" -H \"X-DOLLAR: token/123\\$\"") expect(curlified).toEqual("curl -g --limit-rate 20k -X 'GET' \\\n 'http://example.com' \\\n -H 'X-DOLLAR: token/123$'")
}) })
}) })