Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload Enhancments #79

Merged
merged 9 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Docs.docc/Articles/MakingMultipartFormUploadRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ In this section, you will create a ``HTTPClient`` and use the ``HTTPUploadReques

- First, go to *ViewController.swift* file and create a ``HTTPClient`` using a ``Session`` in the `viewDidLoad` method.

- Second, Create an `UploadRequestRouter`, a one ore more file `Data`, a one or more ``HTTPUploadRequestFile`` and a ``HTTPUploadRequestFormData``.
- Second, Create an `UploadRequestRouter`, a one or more file `Data`, a one or more ``FormFile`` and a ``FormData``.

- Call the `HTTPClient.upload` method and pass the `UploadRequestRouter` and the ``HTTPUploadRequestFormData`` as arguments.
- Call the `HTTPClient.upload` method and pass the `UploadRequestRouter` and the ``FormData`` as arguments.

- Subscribe to the output `Observable` as done below:

Expand All @@ -88,9 +88,9 @@ class ViewController: UIViewController {
let uploadRequestRouter = UploadRequestRouter.default(url: URL(string: "https://example.com/upload/multi")!)
let file1Data = "Example file 1".data(using: .utf8)!
let file2Data = "Example file 2".data(using: .utf8)!
let file1 = HTTPUploadRequestFile(forKey: "file1", withName: "example1.txt", withData: file1Data)!
let file2 = HTTPUploadRequestFile(forKey: "file2", withName: "example2.txt", withData: file1Data)!
let formData = HTTPUploadRequestFormData(parameters: [:], files: [file1, file2])
let file1 = FormFile(forKey: "file1", withName: "example1.txt", withData: file1Data)!
let file2 = FormFile(forKey: "file2", withName: "example2.txt", withData: file1Data)!
let formData = FormData(parameters: [], files: [file1, file2])
httpClient.upload(uploadRequestRouter, formData)
.subscribe(onNext: { (event: HTTPUploadRequestEvent<Model>) in
switch event {
Expand All @@ -113,7 +113,7 @@ class ViewController: UIViewController {

- That's it, you made a multipart form upload request.

- Tip: If you need to upload a file from the disk, you can provide the file url through the `withURL` parameter to the ``HTTPUploadRequestFile`` initializer.
- Tip: If you need to upload a file from the disk, you can provide the file url through the `withURL` parameter to the ``FormFile`` initializer.

- Note: If you choose to upload a file from the disk, you don't have to specify the file name as it will be extracted from the provided file url.

Expand Down
8 changes: 4 additions & 4 deletions Docs.docc/Articles/MakingUploadRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ In this section, you will create a ``HTTPClient`` and use the ``HTTPUploadReques

- First, go to *ViewController.swift* file and create a ``HTTPClient`` using a ``Session`` in the `viewDidLoad` method

- Second, Create an `UploadRequestRouter`, a file `Data` and a ``HTTPUploadRequestFile``.
- Second, Create an `UploadRequestRouter`, a file `Data` and a ``File``.

- Call the `HTTPClient.upload` method and pass the `UploadRequestRouter` and the ``HTTPUploadRequestFile`` as arguments.
- Call the `HTTPClient.upload` method and pass the `UploadRequestRouter` and the ``File`` as arguments.

- Subscribe to the output `Observable` as done below:

Expand All @@ -87,7 +87,7 @@ class ViewController: UIViewController {
// Replace with your upload url
let uploadRequestRouter = UploadRequestRouter.default(url: URL(string: "https://example.com/upload")!)
let fileData = "Example file".data(using: .utf8)!
let file = HTTPUploadRequestFile(forKey: "file", withName: "example.txt", withData: fileData)!
let file = File(forKey: "file", withName: "example.txt", withData: fileData)!
httpClient.upload(uploadRequestRouter, file)
.subscribe(onNext: { (event: HTTPUploadRequestEvent<Model>) in
switch event {
Expand All @@ -110,7 +110,7 @@ class ViewController: UIViewController {

- That's it, you made an upload request.

- Tip: If you need to upload a file from the disk, you can provide the file url through the `withURL` parameter to the ``HTTPUploadRequestFile`` initializer.
- Tip: If you need to upload a file from the disk, you can provide the file url through the `withURL` parameter to the ``File`` initializer.

- Note: If you choose to upload a file from the disk, you don't have to specify the file name as it will be extracted from the provided file url.

Expand Down
144 changes: 118 additions & 26 deletions RxNetworkKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions Source/Common/Request/Logger/Factory/CURLCommandFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// CURLCommandFactory.swift
// RxNetworkKit
//
// Created by Loay Ashraf on 01/10/2024.
//

import Foundation

/// A factory class responsible for making cURL command representations
/// for HTTP requests.
///
/// This class provides methods to create a cURL command string from a given
/// `URLRequest`, including handling different body options like plain data,
/// file uploads, and multipart form data.
final class CURLCommandFactory {

/// Makes cURL command representation for a given request.
///
/// - Parameters:
/// - request: `URLRequest` used to make the cURL command representation.
/// - bodyOption: `HTTPLogBodyOption` that determines how the body is represented in the cURL command.
///
/// - Returns: cURL command representation for the given request.
func make(for request: URLRequest, bodyOption: HTTPLogBodyOption) -> String {
guard let url = request.url else { return "" }
var baseCommand = #"curl "\#(url.absoluteString)""#

if request.httpMethod == "HEAD" {
baseCommand += " --head"
}

var command = [baseCommand]

if let method = request.httpMethod, method != "GET" && method != "HEAD" {
command.append("-X \(method)")
}

if let headers = request.allHTTPHeaderFields {
for (key, value) in headers where key != "Cookie" {
command.append("-H '\(key): \(value)'")
}
}

switch bodyOption {
case .plain:
if let data = request.httpBody,
let body = String(data: data, encoding: .utf8) {
command.append("-d '\(body)'")
}
case .file(let file):
command.append("--upload-file \(file.path ?? "{ Path to the file }")")
case .formData(let formData):
for parameter in formData.parameters {
command.append("-F '\(parameter.key)=\(parameter.value)'")
}

for file in formData.files {
command.append("-F '\(file.key)=@\(file.path ?? "{ Path to the file }");filename=\(file.name)'")
}
}

return command.joined(separator: " \\\n\t")
}

}
116 changes: 116 additions & 0 deletions Source/Common/Request/Logger/Factory/HTTPLogBodyMessageFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// HTTPLogBodyMessageFactory.swift
// RxNetworkKit
//
// Created by Loay Ashraf on 01/10/2024.
//

import Foundation

/// A factory class responsible for making console messages
/// for the body of outgoing HTTP requests based on different logging options.
///
/// This class provides methods to create log messages for plain data,
/// file uploads, and multipart form data. It is utilized by the
/// `HTTPLogger` to format and present the body of HTTP requests in a readable manner.
final class HTTPLogBodyMessageFactory {

/// Makes console body message for the specified body option.
///
/// - Parameters:
/// - bodyOption: `HTTPLogBodyOption` option for how the body is represented in the log message.
///
/// - Returns: `String` of the outgoing request body message.
func make(bodyOption: HTTPLogBodyOption) -> String {
switch bodyOption {
case .plain(let body):
return make(body)
case .file(let file):
return make(file)
case .formData(let formData):
return make(formData)
}
}

/// Makes console message for raw data body.
///
/// - Parameter rawData: Optional `Data` object representing the body.
///
/// - Returns: `String` representation of the raw data body.
private func make(_ rawData: Data?) -> String {
var logBodyMessage = ""
if let rawData = rawData {
if let jsonString = rawData.json {
logBodyMessage += "\n\(jsonString)\n"
} else {
logBodyMessage += "\n\(String(data: rawData, encoding: .utf8) ?? "{ HTTP Body }")\n"
}
}
return logBodyMessage
}

/// Makes console message for a file body.
///
/// - Parameters:
/// - file: `FileType` file details to be printed in place of body.
///
/// - Returns: `String` of the outgoing request body message.
private func make(_ file: FileType) -> String {
var logBodyMessage: String = ""

if let filePath = file.path {
let fileName = file.name
let fileType = file.mimeType.rawValue
let fileSize = file.size.formattedSize
logBodyMessage += "{ File From Disk }\n"
logBodyMessage += "- Name: \(fileName)\n"
logBodyMessage += "- Type: \(fileType)\n"
logBodyMessage += "- Size: \(fileSize)\n"
logBodyMessage += "- Path: \(filePath)"
} else if file.data != nil {
let fileName = file.name
let fileType = file.mimeType.rawValue
let fileSize = file.size.formattedSize
logBodyMessage += "{ File From Memory }\n"
logBodyMessage += "- Name: \(fileName)\n"
logBodyMessage += "- Type: \(fileType)\n"
logBodyMessage += "- Size: \(fileSize)"
}

return logBodyMessage
}

/// Makes console message for multipart form data body.
///
/// - Parameters:
/// - formData: `FormData` form data details to be printed in place of body.
///
/// - Returns: `String` of the outgoing request body message.
private func make(_ formData: FormData) -> String {
var logBodyMessage: String = "\n"
let boundary = formData.boundary
let lineBreak: String = "\r\n"

for parameter in formData.parameters {
logBodyMessage += "--\(formData.boundary + lineBreak)"
logBodyMessage += "Content-Disposition: form-data; name=\"\(parameter.key)\"\(lineBreak + lineBreak)"
logBodyMessage += "\(parameter.value + lineBreak)"
}

for file in formData.files {
logBodyMessage += "--\(boundary + lineBreak)"
logBodyMessage += "Content-Disposition: form-data; name=\"\(file.key)\"; filename=\"\(file.name)\"\(lineBreak)"
logBodyMessage += "Content-Type: \(file.mimeType.rawValue + lineBreak + lineBreak)"

let fileBodyLogMessage = make(file)
logBodyMessage += fileBodyLogMessage

logBodyMessage += lineBreak
}

logBodyMessage += "--\(boundary)--\(lineBreak)"

return logBodyMessage
}

}
Loading
Loading