diff --git a/src/core/curlify.js b/src/core/curlify.js
deleted file mode 100644
index 0735fdccd07..00000000000
--- a/src/core/curlify.js
+++ /dev/null
@@ -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( " " )
-}
diff --git a/src/core/index.js b/src/core/index.js
index 72a32d3ff03..32493a92819 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -53,6 +53,29 @@ export default function SwaggerUI(opts) {
showExtensions: false,
showCommonExtensions: false,
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: [
"get",
"put",
@@ -107,7 +130,8 @@ export default function SwaggerUI(opts) {
spec: {
spec: "",
url: constructorConfig.url
- }
+ },
+ requestSnippets: constructorConfig.requestSnippets
}, constructorConfig.initialState)
}
diff --git a/src/core/plugins/request-snippets/fn.js b/src/core/plugins/request-snippets/fn.js
new file mode 100644
index 00000000000..f692d2d37b3
--- /dev/null
+++ b/src/core/plugins/request-snippets/fn.js
@@ -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();`
+}
diff --git a/src/core/plugins/request-snippets/index.js b/src/core/plugins/request-snippets/index.js
new file mode 100644
index 00000000000..cbda08f996b
--- /dev/null
+++ b/src/core/plugins/request-snippets/index.js
@@ -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
+ }
+ }
+ }
+}
diff --git a/src/core/plugins/request-snippets/request-snippets.jsx b/src/core/plugins/request-snippets/request-snippets.jsx
new file mode 100644
index 00000000000..12bb9b695b7
--- /dev/null
+++ b/src/core/plugins/request-snippets/request-snippets.jsx
@@ -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
+ ?
+ {snippet}
+
+ :
+
+
+ const expanded = this.state.expanded === undefined ? this.props?.requestSnippetsSelectors?.getDefaultExpanded() : this.state.expanded
+ return (
+
+
+
this.setState({expanded: !expanded})}
+ >Snippets
+
+
+ {
+ expanded &&
+
+ {
+ snippetGenerators.map((gen, key) => {
+ return (
onGenChange(key)}>
+
{gen.get("title")}
+ )
+ })
+ }
+
+
+
+
+
+
+
+ {SnippetComponent}
+
+
+ }
+
+
+ )
+ }
+}
diff --git a/src/core/plugins/request-snippets/selectors.js b/src/core/plugins/request-snippets/selectors.js
new file mode 100644
index 00000000000..396f7295209
--- /dev/null
+++ b/src/core/plugins/request-snippets/selectors.js
@@ -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")
+)
diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js
index 147de2aabf8..5e296c64571 100644
--- a/src/core/plugins/spec/actions.js
+++ b/src/core/plugins/spec/actions.js
@@ -5,7 +5,7 @@ import serializeError from "serialize-error"
import isString from "lodash/isString"
import debounce from "lodash/debounce"
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)
// {type: string,payload: Any|Error, meta: obj, error: bool}
@@ -426,9 +426,7 @@ export const executeRequest = (req) =>
const requestBody = oas3Selectors.requestBodyValue(pathName, method)
const requestBodyInclusionSetting = oas3Selectors.requestBodyInclusionSetting(pathName, method)
- if(isJSONObject(requestBody)) {
- req.requestBody = JSON.parse(requestBody)
- } else if(requestBody && requestBody.toJS) {
+ if(requestBody && requestBody.toJS) {
req.requestBody = requestBody
.map(
(val) => {
@@ -445,7 +443,7 @@ export const executeRequest = (req) =>
) || requestBodyInclusionSetting.get(key)
)
.toJS()
- } else{
+ } else {
req.requestBody = requestBody
}
}
diff --git a/src/core/presets/base.js b/src/core/presets/base.js
index 73565cdf7cd..95e7329dd07 100644
--- a/src/core/presets/base.js
+++ b/src/core/presets/base.js
@@ -3,6 +3,7 @@ import layout from "core/plugins/layout"
import spec from "core/plugins/spec"
import view from "core/plugins/view"
import samples from "core/plugins/samples"
+import requestSnippets from "core/plugins/request-snippets"
import logs from "core/plugins/logs"
import swaggerJs from "core/plugins/swagger-js"
import auth from "core/plugins/auth"
@@ -191,6 +192,7 @@ export default function() {
downloadUrlPlugin,
deepLinkingPlugin,
filter,
- onComplete
+ onComplete,
+ requestSnippets
]
}
diff --git a/src/core/syntax-highlighting.js b/src/core/syntax-highlighting.js
index e3e760c5f1b..25e7c31185b 100644
--- a/src/core/syntax-highlighting.js
+++ b/src/core/syntax-highlighting.js
@@ -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 yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml"
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 arta from "react-syntax-highlighter/dist/esm/styles/hljs/arta"
@@ -20,6 +22,8 @@ SyntaxHighlighter.registerLanguage("xml", xml)
SyntaxHighlighter.registerLanguage("yaml", yaml)
SyntaxHighlighter.registerLanguage("http", http)
SyntaxHighlighter.registerLanguage("bash", bash)
+SyntaxHighlighter.registerLanguage("powershell", powershell)
+SyntaxHighlighter.registerLanguage("javascript", javascript)
const styles = {agate, arta, monokai, nord, obsidian, "tomorrow-night": tomorrowNight}
export const availableStyles = Object.keys(styles)
diff --git a/src/core/utils.js b/src/core/utils.js
index 59be73f56f9..853bf68297e 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -32,25 +32,6 @@ const DEFAULT_RESPONSE_KEY = "default"
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) {
if(!isObject(thing))
return {}
diff --git a/test/e2e-cypress/helpers/multiple-examples.js b/test/e2e-cypress/helpers/multiple-examples.js
index 2d024c0db45..88a45043f97 100644
--- a/test/e2e-cypress/helpers/multiple-examples.js
+++ b/test/e2e-cypress/helpers/multiple-examples.js
@@ -208,7 +208,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body
// TODO: use an interceptor instead of 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", () => {
@@ -234,7 +234,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL
// TODO: use an interceptor instead of 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", () => {
@@ -257,7 +257,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
- .contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
+ .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Switch to static docs
.get(".try-out__btn")
.click()
@@ -323,7 +323,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL
// TODO: use an interceptor instead of 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", () => {
@@ -349,7 +349,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the request URL
// TODO: use an interceptor instead of curl
.get(".curl")
- .contains(`-d "${exampleA.serializedValue || exampleA.value}"`)
+ .contains(`-d '${exampleA.serializedValue || exampleA.value}'`)
})
it("static mode toggling: mediaType -> example -> mediaType -> example", () => {
@@ -489,7 +489,7 @@ function RequestBodyPrimitiveTestCases({
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(
- `-d "${customUserInputExpectedCurlSubstring || customUserInput}"`
+ `-d '${customUserInputExpectedCurlSubstring || customUserInput}'`
)
// Choose exampleB
@@ -508,7 +508,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
- .contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
+ .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select")
@@ -530,7 +530,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
- .contains(`-d "${exampleB.serializedValue || exampleB.value}"`)
+ .contains(`-d '${exampleB.serializedValue || exampleB.value}'`)
// Ensure the modified value is still accessible
.get(".opblock-section-request-body .examples-select > select")
@@ -552,7 +552,7 @@ function RequestBodyPrimitiveTestCases({
// Assert on the curl body
// TODO: use an interceptor instead of curl
.get(".curl")
- .contains(`-d "${exampleA.serializedValue || exampleA.value}"`)
+ .contains(`-d '${exampleA.serializedValue || exampleA.value}'`)
// Ensure the modified value is still the same value
.get(".opblock-section-request-body .examples-select > select")
@@ -571,7 +571,7 @@ function RequestBodyPrimitiveTestCases({
// TODO: use an interceptor instead of curl
.get(".curl")
.contains(
- `-d "${customUserInputExpectedCurlSubstring || customUserInput}"`
+ `-d '${customUserInputExpectedCurlSubstring || customUserInput}'`
)
})
diff --git a/test/e2e-cypress/tests/features/multiple-examples-core.js b/test/e2e-cypress/tests/features/multiple-examples-core.js
index 333b797df13..6eef6267198 100644
--- a/test/e2e-cypress/tests/features/multiple-examples-core.js
+++ b/test/e2e-cypress/tests/features/multiple-examples-core.js
@@ -2,7 +2,7 @@
* @prettier
*/
- const {
+const {
ParameterPrimitiveTestCases,
RequestBodyPrimitiveTestCases,
ResponsePrimitiveTestCases,
@@ -256,7 +256,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.get("#operations-default-post_Array")
.click()
.get(".json-schema-form-item > input")
- .then(inputs => {
+ .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"a",
"b",
@@ -276,7 +276,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.get(".parameters-col_description .examples-select > select")
.select("ArrayExampleB")
.get(".json-schema-form-item > input")
- .then(inputs => {
+ .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
@@ -317,7 +317,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.type("5")
// Assert against the input fields
.get(".json-schema-form-item > input")
- .then(inputs => {
+ .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
@@ -351,7 +351,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.select("ArrayExampleB")
// Assert against the input fields
.get(".json-schema-form-item > input")
- .then(inputs => {
+ .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
@@ -367,7 +367,7 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
.select("__MODIFIED__VALUE__")
// Assert that our modified value is back
.get(".json-schema-form-item > input")
- .then(inputs => {
+ .then((inputs) => {
expect(inputs.map((i, el) => el.value).toArray()).to.deep.equal([
"1",
"2",
@@ -566,41 +566,43 @@ describe("OpenAPI 3.0 Multiple Examples - core features", () => {
})
})
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({
operationDomId: "#operations-default-post_Object",
primaryMediaType: "application/json",
// ↓ not a typo, Cypress requires escaping { when using `cy.type`
customUserInput: `{{} "openapiIsCool": true }`,
customExpectedUrlSubstring: "?openapiIsCool=true",
- customUserInputExpectedCurlSubstring: `{\\"openapiIsCool\\":true}`,
+ customUserInputExpectedCurlSubstring: `{ "openapiIsCool": true }`,
exampleA: {
key: "ObjectExampleA",
- serializedValue: `{\\"firstName\\":\\"Kyle\\",\\"lastName\\":\\"Shockey\\",\\"email\\":\\"kyle.shockey@smartbear.com\\"}`,
- value: JSON.stringify(
- {
- firstName: "Kyle",
- lastName: "Shockey",
- email: "kyle.shockey@smartbear.com",
- },
- null,
- 2
- ),
+ serializedValue: exampleA,
+ value: exampleA,
summary: "A user's contact info",
},
exampleB: {
key: "ObjectExampleB",
- serializedValue: `{\\"name\\":\\"Abbey\\",\\"type\\":\\"kitten\\",\\"color\\":\\"calico\\",\\"gender\\":\\"female\\",\\"age\\":\\"11 weeks\\"}`,
- value: JSON.stringify(
- {
- name: "Abbey",
- type: "kitten",
- color: "calico",
- gender: "female",
- age: "11 weeks",
- },
- null,
- 2
- ),
+ serializedValue: exampleB,
+ value: exampleB,
summary: "A wonderful kitten's info",
},
})
diff --git a/test/e2e-cypress/tests/features/oas3-request-body-required.js b/test/e2e-cypress/tests/features/oas3-request-body-required.js
index 62390c62e31..d514f587fe2 100644
--- a/test/e2e-cypress/tests/features/oas3-request-body-required.js
+++ b/test/e2e-cypress/tests/features/oas3-request-body-required.js
@@ -95,7 +95,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".responses-wrapper .curl-command")
.should("exist")
.get(".responses-wrapper .curl-command span")
- .should("contains.text", "\" \"")
+ .should("contains.text", "' '")
})
})
diff --git a/test/mocha/components/live-response.jsx b/test/mocha/components/live-response.jsx
index 0beef9fc123..b75de4bf6ab 100644
--- a/test/mocha/components/live-response.jsx
+++ b/test/mocha/components/live-response.jsx
@@ -3,9 +3,9 @@ import React from "react"
import { fromJSOrdered } from "core/utils"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
-import Curl from "components/curl"
import LiveResponse from "components/live-response"
import ResponseBody from "components/response-body"
+import { RequestSnippets } from "core/plugins/request-snippets/request-snippets"
describe("
", function () {
let request = fromJSOrdered({
@@ -36,7 +36,7 @@ describe("
", function () {
]
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
@@ -54,7 +54,7 @@ describe("
", function () {
let requestForSpy = createSpy().andReturn(request)
let components = {
- curl: Curl,
+ RequestSnippets: RequestSnippets,
responseBody: ResponseBody
}
@@ -69,7 +69,7 @@ describe("
", function () {
return components[c]
},
displayRequestDuration: true,
- getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest })
+ getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest, requestSnippetsEnabled: true })
}
// When
@@ -79,9 +79,9 @@ describe("
", function () {
expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls)
expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls)
- const curl = wrapper.find(Curl)
- expect(curl.length).toEqual(1)
- expect(curl.props().request).toBe(requests[test.expected.request])
+ const snippets = wrapper.find("RequestSnippets")
+ expect(snippets.length).toEqual(1)
+ expect(snippets.props().request).toBe(requests[test.expected.request])
const expectedUrl = requests[test.expected.request].get("url")
expect(wrapper.find("div.request-url pre.microlight").text()).toEqual(expectedUrl)
diff --git a/test/unit/core/curlify.js b/test/unit/core/curlify.js
index 620cd92749b..aa168d07a7c 100644
--- a/test/unit/core/curlify.js
+++ b/test/unit/core/curlify.js
@@ -1,18 +1,19 @@
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"
describe("curlify", 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 = {
url: "http://example.com",
method: "POST",
- body: {
- id: 0,
- name: "doggie",
- status: "available"
- },
+ body,
headers: {
Accept: "application/json",
"content-type": "application/json"
@@ -21,7 +22,7 @@ describe("curlify", function () {
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 () {
@@ -32,7 +33,7 @@ describe("curlify", function () {
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 () {
@@ -46,7 +47,7 @@ describe("curlify", function () {
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 () {
@@ -57,7 +58,7 @@ describe("curlify", function () {
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 () {
@@ -71,24 +72,25 @@ describe("curlify", function () {
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 () {
+ const body = {
+ description: "
Test"
+ }
let req = {
url: "http://swaggerhub.com/v1/one?name=john|smith",
method: "GET",
headers: {
accept: "application/json"
},
- body: {
- description: "
Test"
- }
+ body
}
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\":\"
Test\"}")
+ 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 () {
@@ -105,7 +107,12 @@ describe("curlify", function () {
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\":\"
Test\"}")
+ expect(curlified).toEqual(`curl -X 'POST' \\
+ 'http://swaggerhub.com/v1/one?name=john|smith' \\
+ -H 'accept: application/json' \\
+ -d '{
+ "description": "
Test"
+}'`)
})
it("handles post body with special chars", function () {
@@ -120,7 +127,7 @@ describe("curlify", function () {
let curlified = curl(Im.fromJS(req))
- expect(curlified).toEqual("curl -X POST \"http://swaggerhub.com/v1/one?name=john|smith\" -d {\"description\":\"@prefix nif:
.@prefix itsrdf: .\"}")
+ expect(curlified).toEqual("curl -X 'POST' \\\n 'http://swaggerhub.com/v1/one?name=john|smith' \\\n -d '{\n \"description\": \"@prefix nif: .\\n@prefix itsrdf: .\"\n}'")
})
it("handles delete form with parameters", function () {
@@ -134,7 +141,7 @@ describe("curlify", function () {
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 () {
@@ -150,7 +157,7 @@ describe("curlify", function () {
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 () {
@@ -170,7 +177,7 @@ describe("curlify", function () {
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 () {
@@ -190,7 +197,7 @@ describe("curlify", function () {
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 () {
@@ -210,7 +217,7 @@ describe("curlify", function () {
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 () {
@@ -227,7 +234,7 @@ describe("curlify", function () {
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 () {
@@ -242,7 +249,7 @@ describe("curlify", function () {
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 () {
@@ -267,7 +274,7 @@ describe("curlify", function () {
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))
- 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))
- 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 , no file type provided", function () {
@@ -335,36 +342,10 @@ describe("curlify", function () {
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 () {
let req = {
url: "http://example.com",
@@ -375,7 +356,7 @@ describe("curlify", function () {
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 () {
@@ -388,6 +369,6 @@ describe("curlify", function () {
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$'")
})
})