ft: utils.createObjWithHashedKeys ft: curlify.extractKey test: curlify with array representationbubble
@@ -1,5 +1,19 @@ | |||||
import win from "./window" | import win from "./window" | ||||
/** | |||||
* 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 ){ | export default function curl( request ){ | ||||
let curlified = [] | let curlified = [] | ||||
let type = "" | let type = "" | ||||
@@ -21,11 +35,12 @@ export default function curl( request ){ | |||||
if(type === "multipart/form-data" && request.get("method") === "POST") { | if(type === "multipart/form-data" && request.get("method") === "POST") { | ||||
for( let [ k,v ] of request.get("body").entrySeq()) { | for( let [ k,v ] of request.get("body").entrySeq()) { | ||||
let extractedKey = extractKey(k) | |||||
curlified.push( "-F" ) | curlified.push( "-F" ) | ||||
if (v instanceof win.File) { | if (v instanceof win.File) { | ||||
curlified.push( `"${k}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` ) | |||||
curlified.push(`"${extractedKey}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` ) | |||||
} else { | } else { | ||||
curlified.push( `"${k}=${v}"` ) | |||||
curlified.push(`"${extractedKey}=${v}"` ) | |||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
@@ -67,17 +67,64 @@ export function arrayify (thing) { | |||||
return normalizeArray(thing) | return normalizeArray(thing) | ||||
} | } | ||||
export function fromJSOrdered (js) { | |||||
if(isImmutable(js)) | |||||
export function fromJSOrdered(js) { | |||||
if (isImmutable(js)) { | |||||
return js // Can't do much here | return js // Can't do much here | ||||
if (js instanceof win.File) | |||||
} | |||||
if (js instanceof win.File) { | |||||
return js | |||||
} | |||||
if (!isObject(js)) { | |||||
return js | return js | ||||
} | |||||
if (Array.isArray(js)) { | |||||
return Im.Seq(js).map(fromJSOrdered).toList() | |||||
} | |||||
if (js.entries) { | |||||
// handle multipart/form-data | |||||
const objWithHashedKeys = createObjWithHashedKeys(js) | |||||
return Im.OrderedMap(objWithHashedKeys).map(fromJSOrdered) | |||||
} | |||||
return Im.OrderedMap(js).map(fromJSOrdered) | |||||
} | |||||
return !isObject(js) ? js : | |||||
Array.isArray(js) ? | |||||
Im.Seq(js).map(fromJSOrdered).toList() : | |||||
Im.OrderedMap(js).map(fromJSOrdered) | |||||
/** | |||||
* Convert a FormData object into plain object | |||||
* Append a hashIdx and counter to the key name, if multiple exists | |||||
* if single, key name = <original> | |||||
* if multiple, key name = <original><hashIdx><count> | |||||
* @param {FormData} fdObj - a FormData object | |||||
* @return {Object} - a plain object | |||||
*/ | |||||
export function createObjWithHashedKeys (fdObj) { | |||||
if (!fdObj.entries) { | |||||
return fdObj // not a FormData object with iterable | |||||
} | |||||
const newObj = {} | |||||
const hashIdx = "_**[]" // our internal identifier | |||||
const trackKeys = {} | |||||
for (let pair of fdObj.entries()) { | |||||
if (!newObj[pair[0]] && !(trackKeys[pair[0]] && trackKeys[pair[0]].containsMultiple)) { | |||||
newObj[pair[0]] = pair[1] // first key name: no hash required | |||||
} else { | |||||
if (!trackKeys[pair[0]]) { | |||||
// initiate tracking key for multiple | |||||
trackKeys[pair[0]] = { | |||||
containsMultiple: true, | |||||
length: 1 | |||||
} | |||||
// "reassign" first pair to matching hashed format for multiple | |||||
let hashedKeyFirst = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}` | |||||
newObj[hashedKeyFirst] = newObj[pair[0]] | |||||
// remove non-hashed key of multiple | |||||
delete newObj[pair[0]] // first | |||||
} | |||||
trackKeys[pair[0]].length += 1 | |||||
let hashedKeyCurrent = `${pair[0]}${hashIdx}${trackKeys[pair[0]].length}` | |||||
newObj[hashedKeyCurrent] = pair[1] | |||||
} | |||||
} | |||||
return newObj | |||||
} | } | ||||
export function bindToState(obj, state) { | export function bindToState(obj, state) { | ||||
@@ -143,6 +143,26 @@ describe("curlify", function() { | |||||
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 \"http://example.com\" -H \"content-type: multipart/form-data\" -F \"id=123\" -F \"name=Sahar\"") | ||||
}) | }) | ||||
it("should print a curl with formData that extracts array representation with hashIdx", function() { | |||||
// Note: hashIdx = `_**[]${counter}` | |||||
// Usage of hashIdx is an internal SwaggerUI method to convert formData array into something curlify can handle | |||||
const req = { | |||||
url: "http://example.com", | |||||
method: "POST", | |||||
headers: { "content-type": "multipart/form-data" }, | |||||
body: { | |||||
id: "123", | |||||
"fruits[]_**[]1": "apple", | |||||
"fruits[]_**[]2": "banana", | |||||
"fruits[]_**[]3": "grape" | |||||
} | |||||
} | |||||
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\"") | |||||
}) | |||||
it("should print a curl with formData and file", function() { | it("should print a curl with formData and file", function() { | ||||
var file = new win.File() | var file = new win.File() | ||||
file.name = "file.txt" | file.name = "file.txt" | ||||