Skip to content

Commit

Permalink
Add and test better support for uploading files to multiple variables.
Browse files Browse the repository at this point in the history
  • Loading branch information
designatednerd committed Jun 30, 2020
1 parent 916a5ed commit 4dbde0c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 10 deletions.
33 changes: 23 additions & 10 deletions Sources/Apollo/RequestCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ extension RequestCreator {

// Make sure all fields for files are set to null, or the server won't look
// for the files in the rest of the form data
let fieldsForFiles = Set(files.map { $0.fieldName })
let fieldsForFiles = Set(files.map { $0.fieldName }).sorted()
var fields = requestBody(for: operation, sendOperationIdentifiers: sendOperationIdentifiers)
var variables = fields["variables"] as? GraphQLMap ?? GraphQLMap()
for fieldName in fieldsForFiles {
if
let value = variables[fieldName],
let arrayValue = value as? [JSONEncodable] {
let updatedArray: [JSONEncodable?] = arrayValue.map { _ in nil }
variables.updateValue(updatedArray, forKey: fieldName)
let arrayOfNils: [JSONEncodable?] = arrayValue.map { _ in nil }
variables.updateValue(arrayOfNils, forKey: fieldName)
} else {
variables.updateValue(nil, forKey: fieldName)
}
Expand All @@ -125,20 +125,33 @@ extension RequestCreator {
let operationData = try serializationFormat.serialize(value: fields)
formData.appendPart(data: operationData, name: "operations")

// If there are multiple files for the same field, make sure to include them with indexes for the field. If there are multiple files for different fields, just use the field name.
var map = [String: [String]]()
if files.count == 1 {
let firstFile = files.first!
map["0"] = ["variables.\(firstFile.fieldName)"]
} else {
for (index, file) in files.enumerated() {
map["\(index)"] = ["variables.\(file.fieldName).\(index)"]
var currentIndex = 0

var sortedFiles = [GraphQLFile]()
for fieldName in fieldsForFiles {
let filesForField = files.filter { $0.fieldName == fieldName }
if filesForField.count == 1 {
let firstFile = filesForField.first!
map["\(currentIndex)"] = ["variables.\(firstFile.fieldName)"]
sortedFiles.append(firstFile)
currentIndex += 1
} else {
for (index, file) in filesForField.enumerated() {
map["\(currentIndex)"] = ["variables.\(file.fieldName).\(index)"]
sortedFiles.append(file)
currentIndex += 1
}
}
}

assert(sortedFiles.count == files.count, "Number of sorted files did not equal the number of incoming files - some field name has been left out.")

let mapData = try serializationFormat.serialize(value: map)
formData.appendPart(data: mapData, name: "map")

for (index, file) in files.enumerated() {
for (index, file) in sortedFiles.enumerated() {
formData.appendPart(inputStream: try file.generateInputStream(),
contentLength: file.contentLength,
name: "\(index)",
Expand Down
88 changes: 88 additions & 0 deletions Tests/ApolloTests/RequestCreatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,94 @@ Bravo file content.
}
}

func testMultipleFilesWithMultipleFieldsWithApolloRequestCreator() throws {
let alphaFileURL = self.fileURLForFile(named: "a", extension: "txt")
let alphaFile = try GraphQLFile(fieldName: "uploads",
originalName: "a.txt",
mimeType: "text/plain",
fileURL: alphaFileURL)

let betaFileURL = self.fileURLForFile(named: "b", extension: "txt")
let betaFile = try GraphQLFile(fieldName: "uploads",
originalName: "b.txt",
mimeType: "text/plain",
fileURL: betaFileURL)

let charlieFileUrl = self.fileURLForFile(named: "c", extension: "txt")
let charlieFile = try GraphQLFile(fieldName: "secondField",
originalName: "c.txt",
mimeType: "text/plain",
fileURL: charlieFileUrl)

let data = try apolloRequestCreator.requestMultipartFormData(
for: HeroNameQuery(),
files: [alphaFile, betaFile, charlieFile],
sendOperationIdentifiers: false,
serializationFormat: JSONSerializationFormat.self,
manualBoundary: "TEST.BOUNDARY"
)

let stringToCompare = try self.string(from: data)

if JSONSerialization.dataCanBeSorted() {
let expectedString = """
--TEST.BOUNDARY
Content-Disposition: form-data; name="operations"
{"operationName":"HeroName","query":"query HeroName($episode: Episode) {\\n hero(episode: $episode) {\\n __typename\\n name\\n }\\n}","variables":{"episode":null,\"secondField\":null,\"uploads\":null}}
--TEST.BOUNDARY
Content-Disposition: form-data; name="map"
{"0":["variables.secondField"],"1":["variables.uploads.0"],"2":["variables.uploads.1"]}
--TEST.BOUNDARY
Content-Disposition: form-data; name="0"; filename="c.txt"
Content-Type: text/plain
Charlie file content.
--TEST.BOUNDARY
Content-Disposition: form-data; name="1"; filename="a.txt"
Content-Type: text/plain
Alpha file content.
--TEST.BOUNDARY
Content-Disposition: form-data; name="2"; filename="b.txt"
Content-Type: text/plain
Bravo file content.
--TEST.BOUNDARY--
"""
XCTAssertEqual(stringToCompare, expectedString)
} else {
// Query and operation parameters may be in weird order, so let's at least check that the files got encoded properly.
let endString = """
--TEST.BOUNDARY
Content-Disposition: form-data; name="0"; filename="c.txt"
Content-Type: text/plain
Charlie file content.
--TEST.BOUNDARY
Content-Disposition: form-data; name="1"; filename="a.txt"
Content-Type: text/plain
Alpha file content.
--TEST.BOUNDARY
Content-Disposition: form-data; name="2"; filename="b.txt"
Content-Type: text/plain
Bravo file content.
--TEST.BOUNDARY--
"""
self.checkString(stringToCompare, includes: endString)
}
}


func testRequestBodyWithApolloRequestCreator() {
let query = HeroNameQuery()
let req = apolloRequestCreator.requestBody(for: query, sendOperationIdentifiers: false)
Expand Down

0 comments on commit 4dbde0c

Please sign in to comment.