Skip to content

Commit a7bad6f

Browse files
committed
using result type for returning response
using result type to return the response updating test cases adding extension for multipart form data
1 parent af9ed3c commit a7bad6f

File tree

7 files changed

+268
-62
lines changed

7 files changed

+268
-62
lines changed

HttpUtility.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
86719EA024720BD1002A2AB0 /* HttpUtility.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86719E9624720BD1002A2AB0 /* HttpUtility.framework */; };
1515
86719EA724720BD1002A2AB0 /* HttpUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 86719E9924720BD1002A2AB0 /* HttpUtility.h */; settings = {ATTRIBUTES = (Public, ); }; };
1616
86719EB124720E40002A2AB0 /* HttpUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86719EB024720E40002A2AB0 /* HttpUtility.swift */; };
17+
86E9B56424883E9100B78521 /* Requests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86E9B56324883E9100B78521 /* Requests.swift */; };
1718
/* End PBXBuildFile section */
1819

1920
/* Begin PBXContainerItemProxy section */
@@ -37,6 +38,7 @@
3738
86719E9F24720BD1002A2AB0 /* HttpUtilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HttpUtilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3839
86719EA624720BD1002A2AB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3940
86719EB024720E40002A2AB0 /* HttpUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpUtility.swift; sourceTree = "<group>"; };
41+
86E9B56324883E9100B78521 /* Requests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Requests.swift; sourceTree = "<group>"; };
4042
/* End PBXFileReference section */
4143

4244
/* Begin PBXFrameworksBuildPhase section */
@@ -86,6 +88,7 @@
8688
isa = PBXGroup;
8789
children = (
8890
8656BC60248495700023549D /* Response.swift */,
91+
86E9B56324883E9100B78521 /* Requests.swift */,
8992
);
9093
path = TestModel;
9194
sourceTree = "<group>";
@@ -250,6 +253,7 @@
250253
buildActionMask = 2147483647;
251254
files = (
252255
8656BC61248495700023549D /* Response.swift in Sources */,
256+
86E9B56424883E9100B78521 /* Requests.swift in Sources */,
253257
8656BC5B2483E43D0023549D /* EncodableExtensionUnitTest.swift in Sources */,
254258
8656BC5E2484313F0023549D /* HttpUtilityIntegrationTests.swift in Sources */,
255259
);

HttpUtility/Extensions/EncodableExtension.swift

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,76 @@ import Foundation
1010

1111
extension Encodable
1212
{
13-
func convertToURLQueryItems() -> [URLQueryItem]?
13+
func convertToQueryStringUrl(urlString: String) -> URL?
1414
{
15+
var components = URLComponents(string: urlString)
16+
if(components != nil)
17+
{
18+
do {
19+
let encoder = try JSONEncoder().encode(self)
20+
let requestDictionary = (try? JSONSerialization.jsonObject(with: encoder, options: .allowFragments)).flatMap{$0 as? [String: Any?]}
21+
22+
if(requestDictionary != nil)
23+
{
24+
var queryItems: [URLQueryItem] = []
25+
26+
requestDictionary?.forEach({ (key, value) in
27+
28+
if(value != nil)
29+
{
30+
let strValue = value.map { String(describing: $0) }
31+
if(strValue != nil && strValue?.count != 0)
32+
{
33+
queryItems.append(URLQueryItem(name: key, value: strValue))
34+
}
35+
}
36+
})
37+
38+
components?.queryItems = queryItems
39+
return components?.url!
40+
}
41+
42+
} catch let error {
43+
debugPrint("convertToQueryStringUrl => Error => \(error)")
44+
}
45+
}
46+
47+
debugPrint("convertToQueryStringUrl => Error => Conversion failed, please make sure to pass a valid urlString and try again")
48+
49+
return nil
50+
}
51+
52+
func convertToMultiPartFormData(boundary: String) -> Data
53+
{
54+
let lineBreak = "\r\n"
55+
var requestData = Data()
56+
1557
do {
1658
let encoder = try JSONEncoder().encode(self)
1759
let requestDictionary = (try? JSONSerialization.jsonObject(with: encoder, options: .allowFragments)).flatMap{$0 as? [String: Any?]}
1860

1961
if(requestDictionary != nil)
2062
{
21-
var queryItems: [URLQueryItem] = []
22-
2363
requestDictionary?.forEach({ (key, value) in
24-
2564
if(value != nil)
2665
{
2766
let strValue = value.map { String(describing: $0) }
28-
if(strValue != nil && strValue?.count != 0)
67+
if(strValue != nil && strValue?.isEmpty == false)
2968
{
30-
queryItems.append(URLQueryItem(name: key, value: strValue))
69+
requestData.append("\(lineBreak)--\(boundary)\r\n" .data(using: .utf8)!)
70+
requestData.append("content-disposition: form-data; name=\"\(key)\" \(lineBreak + lineBreak)" .data(using: .utf8)!)
71+
requestData.append("\(strValue!)" .data(using: .utf8)!)
3172
}
3273
}
3374
})
3475

35-
return queryItems
76+
requestData.append("--\(boundary)--\(lineBreak)" .data(using: .utf8)!)
3677
}
3778

3879
} catch let error {
3980
debugPrint(error)
4081
}
4182

42-
return nil
83+
return requestData
4384
}
4485
}

HttpUtility/HttpUtility.swift

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,113 @@ struct HttpHeaderFields
1818
static let contentType = "content-type"
1919
}
2020

21+
struct NetworkError : Error
22+
{
23+
let reason: String?
24+
}
25+
2126
struct HttpUtility
2227
{
23-
func getApiData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(_ result: T?)-> Void)
28+
private var _token: String? = nil
29+
private var _dateFormatter: DateFormatter? = nil
30+
31+
init(token: String?){
32+
_token = token
33+
}
34+
35+
init(dateFormatter: DateFormatter){
36+
_dateFormatter = dateFormatter
37+
}
38+
39+
init(token: String, dateFormatter: DateFormatter)
2440
{
25-
//todo: move this to a common function
26-
// need a date formatter to format the date response received
27-
// use url components for query string
41+
_token = token
42+
_dateFormatter = dateFormatter
43+
}
44+
45+
init(){}
46+
47+
func getApiData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
48+
{
49+
50+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
51+
urlRequest.httpMethod = HttpMethodType.GET
52+
2853
URLSession.shared.dataTask(with: requestUrl) { (responseData, httpUrlResponse, error) in
2954

3055
if(error == nil && responseData != nil && responseData?.count != 0)
3156
{
32-
//parse the responseData here
33-
let decoder = JSONDecoder()
34-
do {
57+
let decoder = self.createJsonDecoder()
58+
do
59+
{
3560
let result = try decoder.decode(T.self, from: responseData!)
36-
_=completionHandler(result)
61+
completionHandler(.success(result))
3762
}
38-
catch let error{
39-
debugPrint("error occured while decoding = \(error)")
63+
catch let error
64+
{
65+
debugPrint(error)
66+
completionHandler(.failure(NetworkError(reason: error.localizedDescription)))
4067
}
41-
}else{
42-
_=completionHandler(nil) //todo: you need to send error that you receive from server
68+
}
69+
else
70+
{
71+
let error = NetworkError(reason: error.debugDescription)
72+
completionHandler(.failure(error))
4373
}
4474

4575
}.resume()
4676
}
4777

48-
func postApiData<T:Decodable>(requestUrl: URL, requestBody: Data, resultType: T.Type, completionHandler:@escaping(_ result: T?)-> Void)
78+
// MARK: - Post Api
79+
func postApiData<T:Decodable>(requestUrl: URL, requestBody: Data, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
4980
{
50-
//todo: make this for posting multi-part form data
51-
var urlRequest = URLRequest(url: requestUrl)
81+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
5282
urlRequest.httpMethod = HttpMethodType.POST
5383
urlRequest.httpBody = requestBody
5484
urlRequest.addValue("application/json", forHTTPHeaderField: HttpHeaderFields.contentType)
5585

5686
URLSession.shared.dataTask(with: urlRequest) { (data, httpUrlResponse, error) in
5787

58-
if(data != nil && data?.count != 0)
88+
if(error == nil && data != nil && data?.count != 0)
5989
{
6090
do {
6191

62-
let response = try JSONDecoder().decode(T.self, from: data!)
63-
_=completionHandler(response)
92+
let decoder = self.createJsonDecoder()
93+
let response = try decoder.decode(T.self, from: data!)
94+
completionHandler(.success(response))
6495
}
65-
catch let decodingError {
96+
catch let decodingError
97+
{
6698
debugPrint(decodingError)
99+
let networkError = NetworkError(reason: decodingError.localizedDescription)
100+
completionHandler(.failure(networkError))
67101
}
68-
}else{
69-
_=completionHandler(nil) //todo: you need to send error that you receive from server
102+
}
103+
else
104+
{
105+
let error = NetworkError(reason: error.debugDescription)
106+
completionHandler(.failure(error))
70107
}
71108

72-
73109
}.resume()
74110
}
111+
112+
// MARK: - Private functions
113+
private func createJsonDecoder() -> JSONDecoder
114+
{
115+
let decoder = JSONDecoder()
116+
decoder.dateDecodingStrategy = _dateFormatter != nil ? .formatted(_dateFormatter!) : .iso8601
117+
return decoder
118+
}
119+
120+
private func createUrlRequest(requestUrl: URL) -> URLRequest
121+
{
122+
var urlRequest = URLRequest(url: requestUrl)
123+
if(_token != nil)
124+
{
125+
urlRequest.addValue(_token!, forHTTPHeaderField: "authorization")
126+
}
127+
128+
return urlRequest
129+
}
75130
}

HttpUtilityTests/ExtensionsTests/EncodableExtensionUnitTest.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,22 @@ import XCTest
1111

1212
class EncodableExtensionUnitTest: XCTestCase {
1313

14-
func test_convertToURLQueryItems_With_SimpleStructure_Returuns_URLQueryItemCollection()
14+
private let exampleUrl = "https://www.example.com"
15+
16+
func test_convertToQueryStringUrl_With_SimpleStructure_Returuns_URLQueryItemCollection()
1517
{
1618
// ARRANGE
1719
struct Simple : Encodable {let name, description: String}
1820
let objSimple = Simple(name: UUID().uuidString, description: UUID().uuidString)
1921

2022
// ACT
21-
let result = objSimple.convertToURLQueryItems()
23+
let result = objSimple.convertToQueryStringUrl(urlString: exampleUrl)!
2224

2325
// ASSERT
2426
XCTAssertNotNil(result)
25-
XCTAssertTrue(result?.count == 2)
2627
}
2728

28-
func test_convertToURLQueryItems_With_IntegerValue_Returuns_URLQueryItemCollection()
29+
func test_convertToQueryStringUrl_With_IntegerValue_Returuns_URLQueryItemCollection()
2930
{
3031
// ARRANGE
3132
struct simple : Encodable {
@@ -36,14 +37,13 @@ class EncodableExtensionUnitTest: XCTestCase {
3637
let objSimple = simple(id: 1, name: UUID().uuidString)
3738

3839
// ACT
39-
let result = objSimple.convertToURLQueryItems()
40+
let result = objSimple.convertToQueryStringUrl(urlString: exampleUrl)
4041

4142
// ASSERT
4243
XCTAssertNotNil(result)
43-
XCTAssertTrue(result?.count == 2)
4444
}
4545

46-
func test_convertToURLQueryItems_With_array_Returuns_URLQueryItemCollection()
46+
func test_convertToQueryStringUrl_With_array_Returuns_URLQueryItemCollection()
4747
{
4848
// ARRANGE
4949
struct simple : Encodable {
@@ -54,14 +54,13 @@ class EncodableExtensionUnitTest: XCTestCase {
5454
let objSimple = simple(id: [1,2,3], name: UUID().uuidString)
5555

5656
// ACT
57-
let result = objSimple.convertToURLQueryItems()
57+
let result = objSimple.convertToQueryStringUrl(urlString: exampleUrl)
5858

5959
// ASSERT
6060
XCTAssertNotNil(result)
61-
XCTAssertTrue(result?.count == 2)
6261
}
6362

64-
func test_convertToURLQueryItems_With_Multiple_DataType_Returuns_URLQueryItemCollection()
63+
func test_convertToQueryStringUrl_With_Multiple_DataType_Returuns_URLQueryItemCollection()
6564
{
6665
// ARRANGE
6766
struct simple : Encodable {
@@ -72,15 +71,27 @@ class EncodableExtensionUnitTest: XCTestCase {
7271
}
7372

7473
let objSimple = simple(id: 1, name: "codecat15", salary: 25000.0, isOnContract: false)
75-
var components = URLComponents(string: "https://www.example.com")
76-
// let expectedQueryString = "https://www.example.com?id=1&name=codecat15&salary=25000&isOnContract=0"
7774

7875
// ACT
79-
let result = objSimple.convertToURLQueryItems()
80-
components?.queryItems = result
76+
let result = objSimple.convertToQueryStringUrl(urlString: exampleUrl)
8177

8278
// ASSERT
8379
XCTAssertNotNil(result)
84-
XCTAssertTrue(result?.count == 4)
80+
}
81+
82+
// MARK: - convert to multipart form data
83+
//todo: need to write more solid test
84+
func test_convertToMultiPartFormData_Returns_Valid_MultiFormData()
85+
{
86+
// ARRANGE
87+
let request = MultiPartFormRequest(name: UUID().uuidString, lastName: UUID().uuidString, dateOfJoining: UUID().uuidString, dateOfBirth: UUID().uuidString, gender: UUID().uuidString, departmentName: UUID().uuidString, managerName: UUID().uuidString)
88+
89+
let boundary = "---------------------------------\(UUID().uuidString)"
90+
91+
// ACT
92+
let formData = request.convertToMultiPartFormData(boundary: boundary)
93+
94+
// ASSERT
95+
XCTAssertNotNil(formData)
8596
}
8697
}

0 commit comments

Comments
 (0)