Skip to content

Commit

Permalink
Upload Enhancments (#79)
Browse files Browse the repository at this point in the history
* feat: enhance plain upload

Resolves: none.

* refactor: relocate logger files to common files group

Resolves: none.

* fix: add all platforms to version check

Resolves: none.

* fix: handle size properly

Resolves: none.

* feat: add memory logger class

Resolves: none.

* feat: enhance plain and multipart upload + applying SRP

Resolves: none.

* feat: update documentation

Resolves: none.

* feat: update documentation

Resolves: none.

* feat: update docs

Resolves: none.
  • Loading branch information
loay-ashraf authored Oct 1, 2024
1 parent a1edbe0 commit 18d71f0
Show file tree
Hide file tree
Showing 39 changed files with 1,470 additions and 512 deletions.
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

0 comments on commit 18d71f0

Please sign in to comment.