diff --git a/Sources/Moya/MoyaProvider+Internal.swift b/Sources/Moya/MoyaProvider+Internal.swift index a36232da1..03a4220c5 100644 --- a/Sources/Moya/MoyaProvider+Internal.swift +++ b/Sources/Moya/MoyaProvider+Internal.swift @@ -84,11 +84,11 @@ public extension MoyaProvider { return self.sendRequest(target, request: request, callbackQueue: callbackQueue, progress: progress, completion: completion) case .uploadFile(let file): return self.sendUploadFile(target, request: request, callbackQueue: callbackQueue, file: file, progress: progress, completion: completion) - case .uploadMultipart(let multipartBody), .uploadCompositeMultipart(let multipartBody, _): - guard !multipartBody.isEmpty && endpoint.method.supportsMultipart else { + case .uploadMultipart(let multipartFormData), .uploadCompositeMultipart(let multipartFormData, _): + guard !multipartFormData.parts.isEmpty && endpoint.method.supportsMultipart else { fatalError("\(target) is not a multipart upload target.") } - return self.sendUploadMultipart(target, request: request, callbackQueue: callbackQueue, multipartBody: multipartBody, progress: progress, completion: completion) + return self.sendUploadMultipart(target, request: request, callbackQueue: callbackQueue, multipartFormData: multipartFormData, progress: progress, completion: completion) case .downloadDestination(let destination), .downloadParameters(_, _, let destination): return self.sendDownloadRequest(target, request: request, callbackQueue: callbackQueue, destination: destination, progress: progress, completion: completion) } @@ -172,9 +172,9 @@ private extension MoyaProvider { } } - func sendUploadMultipart(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, multipartBody: [MultipartFormData], progress: Moya.ProgressBlock? = nil, completion: @escaping Moya.Completion) -> CancellableToken { - let formData = RequestMultipartFormData() - formData.applyMoyaMultipartFormData(multipartBody) + func sendUploadMultipart(_ target: Target, request: URLRequest, callbackQueue: DispatchQueue?, multipartFormData: MultipartFormData, progress: Moya.ProgressBlock? = nil, completion: @escaping Moya.Completion) -> CancellableToken { + let formData = RequestMultipartFormData(fileManager: multipartFormData.fileManager, boundary: multipartFormData.boundary) + formData.applyMoyaMultipartFormData(multipartFormData) let interceptor = self.interceptor(target: target) let uploadRequest: UploadRequest = session.requestQueue.sync { diff --git a/Sources/Moya/MultipartFormData.swift b/Sources/Moya/MultipartFormData.swift index e70d969d0..3fedb211c 100644 --- a/Sources/Moya/MultipartFormData.swift +++ b/Sources/Moya/MultipartFormData.swift @@ -3,6 +3,30 @@ import Alamofire /// Represents "multipart/form-data" for an upload. public struct MultipartFormData: Hashable { + /// `FileManager` to use for file operations, if needed. `FileManager.default` by default. + public let fileManager: FileManager + + /// Separates ``parts`` in the encoded form data. `nil` by default. + public let boundary: String? + + /// Blocks of data to send, separated with ``boundary``. + public let parts: [MultipartFormBodyPart] + + public init(fileManager: FileManager = .default, boundary: String? = nil, parts: [MultipartFormBodyPart]) { + self.fileManager = fileManager + self.boundary = boundary + self.parts = parts + } +} + +extension MultipartFormData: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: MultipartFormBodyPart...) { + self.init(parts: elements) + } +} + +/// Represents the body part of "multipart/form-data" for an upload. +public struct MultipartFormBodyPart: Hashable { /// Method to provide the form data. public enum FormDataProvider: Hashable { @@ -34,11 +58,11 @@ public struct MultipartFormData: Hashable { // MARK: RequestMultipartFormData appending internal extension RequestMultipartFormData { - func append(data: Data, bodyPart: MultipartFormData) { + func append(data: Data, bodyPart: MultipartFormBodyPart) { append(data, withName: bodyPart.name, fileName: bodyPart.fileName, mimeType: bodyPart.mimeType) } - func append(fileURL url: URL, bodyPart: MultipartFormData) { + func append(fileURL url: URL, bodyPart: MultipartFormBodyPart) { if let fileName = bodyPart.fileName, let mimeType = bodyPart.mimeType { append(url, withName: bodyPart.name, fileName: fileName, mimeType: mimeType) } else { @@ -46,12 +70,12 @@ internal extension RequestMultipartFormData { } } - func append(stream: InputStream, length: UInt64, bodyPart: MultipartFormData) { + func append(stream: InputStream, length: UInt64, bodyPart: MultipartFormBodyPart) { append(stream, withLength: length, name: bodyPart.name, fileName: bodyPart.fileName ?? "", mimeType: bodyPart.mimeType ?? "") } - func applyMoyaMultipartFormData(_ multipartBody: [Moya.MultipartFormData]) { - for bodyPart in multipartBody { + func applyMoyaMultipartFormData(_ multipartFormData: Moya.MultipartFormData) { + for bodyPart in multipartFormData.parts { switch bodyPart.provider { case .data(let data): append(data: data, bodyPart: bodyPart) diff --git a/Sources/Moya/Task.swift b/Sources/Moya/Task.swift index 191e25933..b7f8cba00 100644 --- a/Sources/Moya/Task.swift +++ b/Sources/Moya/Task.swift @@ -28,10 +28,10 @@ public enum Task { case uploadFile(URL) /// A "multipart/form-data" upload task. - case uploadMultipart([MultipartFormData]) + case uploadMultipart(MultipartFormData) /// A "multipart/form-data" upload task combined with url parameters. - case uploadCompositeMultipart([MultipartFormData], urlParameters: [String: Any]) + case uploadCompositeMultipart(MultipartFormData, urlParameters: [String: Any]) /// A file download task to a destination. case downloadDestination(DownloadDestination)