From 86159cba49cc341f511a59ec8ceda28d37650654 Mon Sep 17 00:00:00 2001 From: Bruno Coelho <4brunu@users.noreply.github.com> Date: Fri, 13 Mar 2020 14:04:57 +0000 Subject: [PATCH] [Swift] fix URLSession file upload (#5546) * [swift5] - fix URLSession file upload * [swift5] - fix URLSession file upload * [swift5] fix file upload * [swift5] - fix URLSession file upload * [swift] add unit tests for file upload * [swift] update samples copyright --- .../URLSessionImplementations.mustache | 149 ++++++++++++------ .../SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 29 +++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 30 +++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../default/SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 28 +++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 26 ++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 25 ++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- .../SwaggerClientTests/Podfile.lock | 2 +- .../SwaggerClient.xcodeproj/project.pbxproj | 8 + .../SwaggerClientTests/FileUtils.swift | 49 ++++++ .../SwaggerClientTests/PetAPITests.swift | 28 +++- .../SwaggerClientTests/UIImage+Extras.swift | 23 +++ .../OpenAPIs/URLSessionImplementations.swift | 101 ++++++++---- 40 files changed, 1410 insertions(+), 306 deletions(-) create mode 100644 samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift create mode 100644 samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift create mode 100644 samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift create mode 100644 samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift create mode 100644 samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift create mode 100644 samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift create mode 100644 samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift diff --git a/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache b/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache index c1e6e80b9fb7..ccb9220b005e 100644 --- a/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache +++ b/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache @@ -29,7 +29,7 @@ private var urlSessionStore = SynchronizedDictionary() private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -84,7 +84,7 @@ private var urlSessionStore = SynchronizedDictionary() guard let url = URL(string: URLString) else { throw DownloadException.requestMissingURL } - + var originalRequest = URLRequest(url: url) originalRequest.httpMethod = method.rawValue @@ -110,9 +110,9 @@ private var urlSessionStore = SynchronizedDictionary() let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } - + let encoding: ParameterEncoding if fileKeys.count > 0 { encoding = FileUploadEncoding(contentTypeForFormPart: contentTypeForFormPart(fileURL:)) @@ -135,13 +135,13 @@ private var urlSessionStore = SynchronizedDictionary() let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers) let dataTask = urlSession.dataTask(with: request) { [weak self] data, response, error in - + guard let self = self else { return } if let taskCompletionShouldRetry = self.taskCompletionShouldRetry { taskCompletionShouldRetry(data, response, error) { [weak self] shouldRetry in - + guard let self = self else { return } if shouldRetry { @@ -170,7 +170,7 @@ private var urlSessionStore = SynchronizedDictionary() } dataTask.resume() - + } catch { apiResponseQueue.async { cleanupRequest() @@ -266,7 +266,7 @@ private var urlSessionStore = SynchronizedDictionary() let items = contentDisposition.components(separatedBy: ";") - var filename : String? = nil + var filename: String? for contentItem in items { @@ -366,7 +366,7 @@ private var urlSessionStore = SynchronizedDictionary() fileprivate class SessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDelegate { var credential: URLCredential? - + var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { @@ -430,76 +430,117 @@ fileprivate class URLEncoding: ParameterEncoding { } fileprivate class FileUploadEncoding: ParameterEncoding { - + let contentTypeForFormPart: (_ fileURL: URL) -> String? init(contentTypeForFormPart: @escaping (_ fileURL: URL) -> String?) { self.contentTypeForFormPart = contentTypeForFormPart } - - func encode(_ urlRequest: URLRequest, with parameters: [String : Any]?) throws -> URLRequest { - + + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) - + + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) + case let string as String: - + if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } - + case let number as NSNumber: - + if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } - + default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") + + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } - - body.append(data) - - body.append("\r\n") - - body.append("--\(boundary)--\r\n") - urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") + + body.append(data) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + return urlRequest } - + func mimeType(for url: URL) -> String { let pathExtension = url.pathExtension @@ -510,15 +551,15 @@ fileprivate class FileUploadEncoding: ParameterEncoding { } return "application/octet-stream" } - + } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/Podfile.lock index e8fcc297cc62..5ded1f788906 100644 --- a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/Podfile.lock @@ -20,4 +20,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 7eef7443abeb..c9ad03859708 100644 --- a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; }; 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; }; 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; }; + A5EA12542419387200E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12522419387100E30FC3 /* FileUtils.swift */; }; + A5EA12552419387200E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12532419387100E30FC3 /* UIImage+Extras.swift */; }; FB5CCC7EFA680BB2746B695B /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */; }; /* End PBXBuildFile section */ @@ -47,6 +49,8 @@ 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = ""; }; 7F98CC8B18E5FA9213F6A68D /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A5EA12522419387100E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + A5EA12532419387100E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; ACB80AC61FA8D8916D4559AA /* Pods-SwaggerClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.release.xcconfig"; sourceTree = ""; }; C07EC0A94AA0F86D60668B32 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E43FC34A9681D65ED44EE914 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = ""; }; @@ -135,6 +139,8 @@ 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */, 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */, 1A501F47219C3DC600F372F6 /* DateFormatTests.swift */, + A5EA12522419387100E30FC3 /* FileUtils.swift */, + A5EA12532419387100E30FC3 /* UIImage+Extras.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -323,8 +329,10 @@ files = ( 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */, 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */, + A5EA12552419387200E30FC3 /* UIImage+Extras.swift in Sources */, 1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */, 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */, + A5EA12542419387200E30FC3 /* FileUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 6be5bc6d29fe..f94dcf84fbf5 100644 --- a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -61,8 +61,35 @@ class PetAPITests: XCTestCase { self.waitForExpectations(timeout: testTimeout, handler: nil) } + + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") - func test3DeletePet() { + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL) { (_, error) in + guard error == nil else { + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + return + } + + FileUtils.deleteFile(fileURL: imageURL) + expectation.fulfill() + } + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000) { (_, error) in diff --git a/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/petstore/swift5/combineLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/combineLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/combineLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/combineLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/Podfile.lock index 8749d2533982..a621fdeec885 100644 --- a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 4394da5614bb..0be50e9f6f19 100644 --- a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + A5EA12582419390400E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12562419390400E30FC3 /* FileUtils.swift */; }; + A5EA12592419390400E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12572419390400E30FC3 /* UIImage+Extras.swift */; }; B024164FBFF71BF644D4419A /* Pods_SwaggerClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 177A58DD5CF63F2989335DCC /* Pods_SwaggerClient.framework */; }; B1D0246C8960F47A60098F37 /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F96A0131101344CC5406CB3 /* Pods_SwaggerClientTests.framework */; }; B596E4BD205657A500B46F03 /* APIHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596E4BC205657A500B46F03 /* APIHelperTests.swift */; }; @@ -36,6 +38,8 @@ 4EF2021609D112A6F5AE0F55 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = ""; }; 6F96A0131101344CC5406CB3 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8D99518E8E05FD856A952698 /* Pods-SwaggerClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.debug.xcconfig"; sourceTree = ""; }; + A5EA12562419390400E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + A5EA12572419390400E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; B596E4BC205657A500B46F03 /* APIHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHelperTests.swift; sourceTree = ""; }; EAEC0BBE1D4E30CE00C908A3 /* SwaggerClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwaggerClient.app; sourceTree = BUILT_PRODUCTS_DIR; }; EAEC0BC11D4E30CE00C908A3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -133,6 +137,8 @@ EAEC0BE51D4E379000C908A3 /* StoreAPITests.swift */, EAEC0BE71D4E38CB00C908A3 /* UserAPITests.swift */, B596E4BC205657A500B46F03 /* APIHelperTests.swift */, + A5EA12562419390400E30FC3 /* FileUtils.swift */, + A5EA12572419390400E30FC3 /* UIImage+Extras.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -311,8 +317,10 @@ files = ( B596E4BD205657A500B46F03 /* APIHelperTests.swift in Sources */, EAEC0BE81D4E38CB00C908A3 /* UserAPITests.swift in Sources */, + A5EA12592419390400E30FC3 /* UIImage+Extras.swift in Sources */, EAEC0BE61D4E379000C908A3 /* StoreAPITests.swift in Sources */, EAEC0BE41D4E330700C908A3 /* PetAPITests.swift in Sources */, + A5EA12582419390400E30FC3 /* FileUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 6de0bf826ba7..6136098ee576 100644 --- a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -76,7 +76,35 @@ class PetAPITests: XCTestCase { self.waitForExpectations(timeout: testTimeout, handler: nil) } - func test3DeletePet() { + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") + + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL).sink(receiveCompletion: { (completion) in + switch completion { + case .failure: + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + case .finished:() + } + }, receiveValue: { _ in + FileUtils.deleteFile(fileURL: imageURL) + + expectation.fulfill() + }).store(in: &anyCancellables) + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000).sink(receiveCompletion: { (completion) in switch completion { diff --git a/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/combineLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/petstore/swift5/default/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/default/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/default/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/default/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/default/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/default/SwaggerClientTests/Podfile.lock index 8749d2533982..a621fdeec885 100644 --- a/samples/client/petstore/swift5/default/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/default/SwaggerClientTests/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 91b653b9d318..ca2ec05d5dca 100644 --- a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; }; 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; }; 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; }; + A5EA124F241901AA00E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA124E241901AA00E30FC3 /* UIImage+Extras.swift */; }; + A5EA1251241905F000E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA1250241905F000E30FC3 /* FileUtils.swift */; }; FB5CCC7EFA680BB2746B695B /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */; }; /* End PBXBuildFile section */ @@ -47,6 +49,8 @@ 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = ""; }; 7F98CC8B18E5FA9213F6A68D /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A5EA124E241901AA00E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; + A5EA1250241905F000E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; ACB80AC61FA8D8916D4559AA /* Pods-SwaggerClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.release.xcconfig"; sourceTree = ""; }; C07EC0A94AA0F86D60668B32 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E43FC34A9681D65ED44EE914 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = ""; }; @@ -135,6 +139,8 @@ 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */, 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */, 1A501F47219C3DC600F372F6 /* DateFormatTests.swift */, + A5EA124E241901AA00E30FC3 /* UIImage+Extras.swift */, + A5EA1250241905F000E30FC3 /* FileUtils.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -322,7 +328,9 @@ 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */, 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */, 1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */, + A5EA1251241905F000E30FC3 /* FileUtils.swift in Sources */, 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */, + A5EA124F241901AA00E30FC3 /* UIImage+Extras.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 6be5bc6d29fe..31f90f6acfab 100644 --- a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -62,7 +62,33 @@ class PetAPITests: XCTestCase { self.waitForExpectations(timeout: testTimeout, handler: nil) } - func test3DeletePet() { + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") + + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL) { (_, error) in + guard error == nil else { + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + return + } + + FileUtils.deleteFile(fileURL: imageURL) + expectation.fulfill() + } + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000) { (_, error) in diff --git a/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/default/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/petstore/swift5/nonPublicApi/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/nonPublicApi/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 7128797f067d..5a0e344f20eb 100644 --- a/samples/client/petstore/swift5/nonPublicApi/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/nonPublicApi/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ internal class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ internal class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/objcCompatible/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/objcCompatible/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/objcCompatible/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/objcCompatible/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/promisekitLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/promisekitLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/promisekitLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/promisekitLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/Podfile.lock index fa73b8359807..9e4fdf683673 100644 --- a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/Podfile.lock @@ -20,4 +20,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 1e0ed2f7cad7..47fa20866cea 100644 --- a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; }; 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; }; 751C65B82F596107A3DC8ED9 /* Pods_SwaggerClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F60AECFF321A25553B6A5B0 /* Pods_SwaggerClient.framework */; }; + A5EA125C2419398500E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA125A2419398500E30FC3 /* UIImage+Extras.swift */; }; + A5EA125D2419398500E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA125B2419398500E30FC3 /* FileUtils.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,6 +46,8 @@ 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreAPITests.swift; sourceTree = ""; }; 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = ""; }; 8F60AECFF321A25553B6A5B0 /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A5EA125A2419398500E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; + A5EA125B2419398500E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; A638467ACFB30852DEA51F7A /* Pods-SwaggerClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.debug.xcconfig"; sourceTree = ""; }; B4B2BEC2ECA535C616F2F3FE /* Pods-SwaggerClientTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.release.xcconfig"; sourceTree = ""; }; C07EC0A94AA0F86D60668B32 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -132,6 +136,8 @@ 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */, 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */, 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */, + A5EA125B2419398500E30FC3 /* FileUtils.swift */, + A5EA125A2419398500E30FC3 /* UIImage+Extras.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -312,7 +318,9 @@ files = ( 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */, 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */, + A5EA125C2419398500E30FC3 /* UIImage+Extras.swift in Sources */, 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */, + A5EA125D2419398500E30FC3 /* FileUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 46fa30ba0cae..bda06aeac988 100644 --- a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -50,8 +50,32 @@ class PetAPITests: XCTestCase { } self.waitForExpectations(timeout: testTimeout, handler: nil) } + + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") - func test3DeletePet() { + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL).done { _ in + FileUtils.deleteFile(fileURL: imageURL) + expectation.fulfill() + }.catch { _ in + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + } + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000).done { expectation.fulfill() diff --git a/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/rxswiftLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/rxswiftLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/rxswiftLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/rxswiftLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/Podfile.lock index f83481941b8a..843ccc1723ed 100644 --- a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/Podfile.lock @@ -20,4 +20,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 4d5fe1313792..f04b8be9b6a9 100644 --- a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + A5EA1260241941BE00E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA125E241941BE00E30FC3 /* FileUtils.swift */; }; + A5EA1261241941BE00E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA125F241941BE00E30FC3 /* UIImage+Extras.swift */; }; B024164FBFF71BF644D4419A /* Pods_SwaggerClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 177A58DD5CF63F2989335DCC /* Pods_SwaggerClient.framework */; }; B1D0246C8960F47A60098F37 /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F96A0131101344CC5406CB3 /* Pods_SwaggerClientTests.framework */; }; B596E4BD205657A500B46F03 /* APIHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596E4BC205657A500B46F03 /* APIHelperTests.swift */; }; @@ -36,6 +38,8 @@ 4EF2021609D112A6F5AE0F55 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = ""; }; 6F96A0131101344CC5406CB3 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8D99518E8E05FD856A952698 /* Pods-SwaggerClient.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.debug.xcconfig"; sourceTree = ""; }; + A5EA125E241941BE00E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + A5EA125F241941BE00E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; B596E4BC205657A500B46F03 /* APIHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIHelperTests.swift; sourceTree = ""; }; EAEC0BBE1D4E30CE00C908A3 /* SwaggerClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwaggerClient.app; sourceTree = BUILT_PRODUCTS_DIR; }; EAEC0BC11D4E30CE00C908A3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -133,6 +137,8 @@ EAEC0BE51D4E379000C908A3 /* StoreAPITests.swift */, EAEC0BE71D4E38CB00C908A3 /* UserAPITests.swift */, B596E4BC205657A500B46F03 /* APIHelperTests.swift */, + A5EA125E241941BE00E30FC3 /* FileUtils.swift */, + A5EA125F241941BE00E30FC3 /* UIImage+Extras.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -313,8 +319,10 @@ files = ( B596E4BD205657A500B46F03 /* APIHelperTests.swift in Sources */, EAEC0BE81D4E38CB00C908A3 /* UserAPITests.swift in Sources */, + A5EA1261241941BE00E30FC3 /* UIImage+Extras.swift in Sources */, EAEC0BE61D4E379000C908A3 /* StoreAPITests.swift in Sources */, EAEC0BE41D4E330700C908A3 /* PetAPITests.swift in Sources */, + A5EA1260241941BE00E30FC3 /* FileUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 5b759947421d..d239a12febfe 100644 --- a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -66,7 +66,30 @@ class PetAPITests: XCTestCase { self.waitForExpectations(timeout: testTimeout, handler: nil) } - func test3DeletePet() { + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") + + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL).subscribe(onNext: { pet in + FileUtils.deleteFile(fileURL: imageURL) + expectation.fulfill() + }, onError: { _ in + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + }).disposed(by: disposeBag) + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000).subscribe(onNext: { expectation.fulfill() diff --git a/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/petstore/swift5/urlsessionLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/petstore/swift5/urlsessionLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift index 339f484cd9d6..af3c536b6eff 100644 --- a/samples/client/petstore/swift5/urlsessionLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/petstore/swift5/urlsessionLibrary/PetstoreClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {} diff --git a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/Podfile.lock b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/Podfile.lock index 8749d2533982..a621fdeec885 100644 --- a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/Podfile.lock +++ b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.0 diff --git a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj index 91b653b9d318..f3d009d59af9 100644 --- a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj +++ b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; }; 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; }; 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; }; + A5EA12642419439700E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12622419439700E30FC3 /* FileUtils.swift */; }; + A5EA12652419439700E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12632419439700E30FC3 /* UIImage+Extras.swift */; }; FB5CCC7EFA680BB2746B695B /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */; }; /* End PBXBuildFile section */ @@ -47,6 +49,8 @@ 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = ""; }; 7F98CC8B18E5FA9213F6A68D /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A5EA12622419439700E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + A5EA12632419439700E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = ""; }; ACB80AC61FA8D8916D4559AA /* Pods-SwaggerClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.release.xcconfig"; sourceTree = ""; }; C07EC0A94AA0F86D60668B32 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E43FC34A9681D65ED44EE914 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = ""; }; @@ -135,6 +139,8 @@ 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */, 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */, 1A501F47219C3DC600F372F6 /* DateFormatTests.swift */, + A5EA12622419439700E30FC3 /* FileUtils.swift */, + A5EA12632419439700E30FC3 /* UIImage+Extras.swift */, ); path = SwaggerClientTests; sourceTree = ""; @@ -321,8 +327,10 @@ files = ( 6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */, 6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */, + A5EA12652419439700E30FC3 /* UIImage+Extras.swift in Sources */, 1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */, 6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */, + A5EA12642419439700E30FC3 /* FileUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift new file mode 100644 index 000000000000..365a55b06b18 --- /dev/null +++ b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/FileUtils.swift @@ -0,0 +1,49 @@ +// +// FileUtils.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +class FileUtils { + static func saveImage(imageName: String, image: UIImage) -> URL? { + guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { + return nil + } + + let fileName = imageName + let fileURL = documentsDirectory.appendingPathComponent(fileName) + guard let data = image.jpegData(compressionQuality: 1) else { return nil } + + //Checks if file exists, removes it if so. + deleteFile(fileURL: fileURL) + + do { + try data.write(to: fileURL) + } catch let error { + print("error saving file with error", error) + return nil + } + + return fileURL + } + + @discardableResult + static func deleteFile(fileURL: URL) -> Bool { + if FileManager.default.fileExists(atPath: fileURL.path) { + do { + try FileManager.default.removeItem(atPath: fileURL.path) + print("Removed old image") + return true + } catch let removeError { + print("couldn't remove file at path", removeError) + return false + } + } + return false + } + +} diff --git a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift index 6be5bc6d29fe..31f90f6acfab 100644 --- a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift +++ b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/PetAPITests.swift @@ -62,7 +62,33 @@ class PetAPITests: XCTestCase { self.waitForExpectations(timeout: testTimeout, handler: nil) } - func test3DeletePet() { + func test3UploadFile() { + let expectation = self.expectation(description: "testUploadFile") + + let imageName = UUID().uuidString + ".png" + + guard + let image = UIImage(color: .red, size: CGSize(width: 10, height: 10)), + let imageURL = FileUtils.saveImage(imageName: imageName, image: image) + else { + fatalError() + } + + PetAPI.uploadFile(petId: 1000, additionalMetadata: "additionalMetadata", file: imageURL) { (_, error) in + guard error == nil else { + FileUtils.deleteFile(fileURL: imageURL) + XCTFail("error uploading file") + return + } + + FileUtils.deleteFile(fileURL: imageURL) + expectation.fulfill() + } + + self.waitForExpectations(timeout: testTimeout, handler: nil) + } + + func test4DeletePet() { let expectation = self.expectation(description: "testDeletePet") PetAPI.deletePet(petId: 1000) { (_, error) in diff --git a/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift new file mode 100644 index 000000000000..e6061c750df4 --- /dev/null +++ b/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClientTests/UIImage+Extras.swift @@ -0,0 +1,23 @@ +// +// UIImage+Extras.swift +// SwaggerClientTests +// +// Created by Bruno Coelho on 11/03/2020. +// Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) +// + +import UIKit + +extension UIImage { + convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) { + let rect = CGRect(origin: .zero, size: size) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + color.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let cgImage = image?.cgImage else { return nil } + self.init(cgImage: cgImage) + } +} diff --git a/samples/client/test/swift5/default/TestClient/Classes/OpenAPIs/URLSessionImplementations.swift b/samples/client/test/swift5/default/TestClient/Classes/OpenAPIs/URLSessionImplementations.swift index 762ea264b2fd..1ea239f200ec 100644 --- a/samples/client/test/swift5/default/TestClient/Classes/OpenAPIs/URLSessionImplementations.swift +++ b/samples/client/test/swift5/default/TestClient/Classes/OpenAPIs/URLSessionImplementations.swift @@ -29,7 +29,7 @@ open class URLSessionRequestBuilder: RequestBuilder { private var observation: NSKeyValueObservation? deinit { - observation?.invalidate() + observation?.invalidate() } // swiftlint:disable:next weak_delegate @@ -110,7 +110,7 @@ open class URLSessionRequestBuilder: RequestBuilder { let parameters: [String: Any] = self.parameters ?? [:] - let fileKeys = parameters.filter { $1 is NSURL } + let fileKeys = parameters.filter { $1 is URL } .map { $0.0 } let encoding: ParameterEncoding @@ -441,58 +441,99 @@ private class FileUploadEncoding: ParameterEncoding { var urlRequest = urlRequest - for (k, v) in parameters ?? [:] { - switch v { + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + switch value { case let fileURL as URL: - let fileData = try Data(contentsOf: fileURL) - - let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) - - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: fileURL.lastPathComponent, data: fileData, mimeType: mimetype) + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) case let string as String: if let data = string.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } case let number as NSNumber: if let data = number.stringValue.data(using: .utf8) { - urlRequest = configureFileUploadRequest(urlRequest: urlRequest, name: k, data: data, mimeType: nil) + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) } default: - fatalError("Unprocessable value \(v) with key \(k)") + fatalError("Unprocessable value \(value) with key \(key)") } } + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)--") + + urlRequest.httpBody = body + return urlRequest } - private func configureFileUploadRequest(urlRequest: URLRequest, name: String, data: Data, mimeType: String?) -> URLRequest { + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { var urlRequest = urlRequest - var body = urlRequest.httpBody ?? Data() + var body = urlRequest.httpBody.orEmpty - // https://stackoverflow.com/a/26163136/976628 - let boundary = "Boundary-\(UUID().uuidString)" - urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + let fileData = try Data(contentsOf: fileURL) + + let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent body.append("--\(boundary)\r\n") - body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n\r\n") - if let mimeType = mimeType { - body.append("Content-Type: \(mimeType)\r\n\r\n") - } + body.append("Content-Type: \(mimetype)\r\n\r\n") + + body.append(fileData) + + body.append("\r\n\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + body.append("--\(boundary)\r\n") + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n") body.append(data) - body.append("\r\n") - - body.append("--\(boundary)--\r\n") + body.append("\r\n\r\n") urlRequest.httpBody = body @@ -514,11 +555,11 @@ private class FileUploadEncoding: ParameterEncoding { } fileprivate extension Data { - /// Append string to NSMutableData + /// Append string to Data /// - /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to NSData, and then add that data to the NSMutableData, this wraps it in a nice convenient little extension to NSMutableData. This converts using UTF-8. + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. /// - /// - parameter string: The string to be added to the `NSMutableData`. + /// - parameter string: The string to be added to the `Data`. mutating func append(_ string: String) { if let data = string.data(using: .utf8) { @@ -527,4 +568,10 @@ fileprivate extension Data { } } +fileprivate extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + extension JSONDataEncoding: ParameterEncoding {}