Skip to content

Commit cd02b14

Browse files
authored
Merge pull request #3 from codecat15/multipart-form-data-format
minor code improvements
2 parents e09173b + 785d532 commit cd02b14

File tree

5 files changed

+345
-122
lines changed

5 files changed

+345
-122
lines changed

HttpUtility.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@
321321
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
322322
GCC_WARN_UNUSED_FUNCTION = YES;
323323
GCC_WARN_UNUSED_VARIABLE = YES;
324-
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
324+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
325325
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
326326
MTL_FAST_MATH = YES;
327327
ONLY_ACTIVE_ARCH = YES;
@@ -378,7 +378,7 @@
378378
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
379379
GCC_WARN_UNUSED_FUNCTION = YES;
380380
GCC_WARN_UNUSED_VARIABLE = YES;
381-
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
381+
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
382382
MTL_ENABLE_DEBUG_INFO = NO;
383383
MTL_FAST_MATH = YES;
384384
SDKROOT = iphoneos;

HttpUtility/HttpUtility.swift

Lines changed: 121 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
import Foundation
1010

11-
struct HttpMethodType {
12-
static let GET = "GET"
13-
static let POST = "POST"
11+
struct HttpMethod : Equatable, Hashable {
12+
static let get = "GET"
13+
static let post = "POST"
14+
static let put = "PUT"
15+
static let delete = "DELETE"
1416
}
1517

1618
struct HttpHeaderFields
@@ -24,150 +26,156 @@ struct NetworkError : Error
2426
let httpStatusCode: Int?
2527
}
2628

29+
enum HttpMethods
30+
{
31+
case get
32+
case post
33+
case put
34+
case delete
35+
}
36+
2737
struct HttpUtility
2838
{
2939
private var _token: String? = nil
30-
private var _dateFormatter: DateFormatter? = nil
40+
private var _customJsonDecoder: JSONDecoder? = nil
3141

3242
init(token: String?){
3343
_token = token
3444
}
35-
36-
init(dateFormatter: DateFormatter){
37-
_dateFormatter = dateFormatter
38-
}
39-
40-
init(token: String, dateFormatter: DateFormatter)
41-
{
45+
46+
init(token: String?, decoder: JSONDecoder?){
4247
_token = token
43-
_dateFormatter = dateFormatter
48+
_customJsonDecoder = decoder
49+
}
50+
51+
init(WithJsonDecoder decoder: JSONDecoder){
52+
_customJsonDecoder = decoder
4453
}
4554

4655
init(){}
47-
48-
func getData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
56+
57+
func request<T:Decodable>(requestUrl: URL, method: HttpMethods, requestBody: Data? = nil, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
4958
{
50-
51-
var urlRequest = createUrlRequest(requestUrl: requestUrl)
52-
urlRequest.httpMethod = HttpMethodType.GET
53-
54-
URLSession.shared.dataTask(with: requestUrl) { (responseData, httpUrlResponse, error) in
55-
56-
//todo: this code can be added into a common function
57-
let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode
58-
if(error == nil && responseData != nil && responseData?.count != 0)
59-
{
60-
let decoder = self.createJsonDecoder()
61-
do
62-
{
63-
let result = try decoder.decode(T.self, from: responseData!)
64-
completionHandler(.success(result))
65-
}
66-
catch let error
67-
{
68-
debugPrint(error)
69-
completionHandler(.failure(NetworkError(reason: error.localizedDescription, httpStatusCode: statusCode)))
70-
}
71-
}
72-
else
73-
{
74-
let error = NetworkError(reason: error.debugDescription,httpStatusCode: statusCode)
75-
completionHandler(.failure(error))
76-
}
77-
78-
}.resume()
59+
switch method
60+
{
61+
case .get:
62+
getData(requestUrl: requestUrl, resultType: T.self) { completionHandler($0)}
63+
break
64+
65+
case .post:
66+
postData(requestUrl: requestUrl, requestBody: requestBody!, resultType: T.self) { completionHandler($0)}
67+
break
68+
69+
case .put:
70+
putData(requestUrl: requestUrl, resultType: T.self) { completionHandler($0)}
71+
break
72+
73+
case .delete:
74+
deleteData(requestUrl: requestUrl, resultType: T.self) { completionHandler($0)}
75+
break
76+
}
7977
}
80-
81-
// MARK: - Post Api
82-
func postData<T:Decodable>(requestUrl: URL, requestBody: Data, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
78+
79+
// MARK: - Private functions
80+
private func createJsonDecoder() -> JSONDecoder
8381
{
84-
var urlRequest = createUrlRequest(requestUrl: requestUrl)
85-
urlRequest.httpMethod = HttpMethodType.POST
86-
urlRequest.httpBody = requestBody
87-
urlRequest.addValue("application/json", forHTTPHeaderField: HttpHeaderFields.contentType)
88-
89-
URLSession.shared.dataTask(with: urlRequest) { (data, httpUrlResponse, error) in
90-
let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode
91-
if(error == nil && data != nil && data?.count != 0)
92-
{
93-
do {
94-
95-
let decoder = self.createJsonDecoder()
96-
let response = try decoder.decode(T.self, from: data!)
97-
completionHandler(.success(response))
98-
}
99-
catch let decodingError
100-
{
101-
debugPrint(decodingError)
102-
let networkError = NetworkError(reason: decodingError.localizedDescription, httpStatusCode: statusCode)
103-
completionHandler(.failure(networkError))
104-
}
105-
}
106-
else
107-
{
108-
let error = NetworkError(reason: error.debugDescription, httpStatusCode: statusCode)
109-
completionHandler(.failure(error))
110-
}
111-
112-
}.resume()
82+
let decoder = _customJsonDecoder != nil ? _customJsonDecoder! : JSONDecoder()
83+
if(_customJsonDecoder == nil)
84+
{
85+
decoder.dateDecodingStrategy = .iso8601
86+
}
87+
return decoder
11388
}
11489

115-
func postMultipartFormData<T:Decodable>(requestUrl: URL, multiPartRequestBody: Data, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
90+
private func createUrlRequest(requestUrl: URL) -> URLRequest
11691
{
11792
var urlRequest = URLRequest(url: requestUrl)
93+
if(_token != nil)
94+
{
95+
urlRequest.addValue(_token!, forHTTPHeaderField: "authorization")
96+
}
11897

119-
urlRequest.httpMethod = HttpMethodType.POST
120-
121-
let boundary = "---------------------------------\(UUID().uuidString)"
122-
urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "content-type")
123-
urlRequest.addValue("\(multiPartRequestBody.count)", forHTTPHeaderField: "content-length")
124-
urlRequest.httpBody = multiPartRequestBody
125-
126-
debugPrint("multipart form data => \(String(describing: String(data: multiPartRequestBody, encoding: .utf8)))")
127-
128-
URLSession.shared.dataTask(with: urlRequest) { (data, httpUrlResponse, error) in
98+
return urlRequest
99+
}
100+
101+
private func decodeJsonResponse<T: Decodable>(data: Data, responseType: T.Type) -> T?
102+
{
103+
let decoder = self.createJsonDecoder()
104+
do{
105+
return try decoder.decode(T.self, from: data)
106+
}catch let error{
107+
debugPrint("deocding error =>\(error)")
108+
}
109+
return nil
110+
}
111+
112+
private func performOperation<T: Decodable>(requestUrl: URLRequest, responseType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>) -> Void)
113+
{
114+
URLSession.shared.dataTask(with: requestUrl) { (data, httpUrlResponse, error) in
115+
129116
let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode
130117
if(error == nil && data != nil && data?.count != 0)
131118
{
132-
do {
133-
let decoder = self.createJsonDecoder()
134-
let response = try decoder.decode(T.self, from: data!)
119+
let response = self.decodeJsonResponse(data: data!, responseType: T.self)
120+
121+
if(response != nil){
135122
completionHandler(.success(response))
136-
}
137-
catch let decodingError
138-
{
139-
debugPrint(decodingError)
140-
let networkError = NetworkError(reason: decodingError.localizedDescription, httpStatusCode: statusCode)
141-
completionHandler(.failure(networkError))
123+
}else{
124+
completionHandler(.failure(NetworkError(reason: "decoding error", httpStatusCode: statusCode)))
142125
}
143126
}
144127
else
145128
{
146-
debugPrint(error.debugDescription)
147-
let networkError = NetworkError(reason: error.debugDescription, httpStatusCode: statusCode)
129+
let networkError = NetworkError(reason: error.debugDescription,httpStatusCode: statusCode)
148130
completionHandler(.failure(networkError))
149131
}
150-
132+
151133
}.resume()
152-
153134
}
154-
155-
// MARK: - Private functions
156-
private func createJsonDecoder() -> JSONDecoder
135+
136+
// MARK: - GET Api
137+
private func getData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
157138
{
158-
let decoder = JSONDecoder()
159-
decoder.dateDecodingStrategy = _dateFormatter != nil ? .formatted(_dateFormatter!) : .iso8601
160-
return decoder
139+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
140+
urlRequest.httpMethod = HttpMethod.get
141+
142+
performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in
143+
completionHandler(result)
144+
}
161145
}
162-
163-
private func createUrlRequest(requestUrl: URL) -> URLRequest
146+
147+
// MARK: - POST Api
148+
private func postData<T:Decodable>(requestUrl: URL, requestBody: Data, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
164149
{
165-
var urlRequest = URLRequest(url: requestUrl)
166-
if(_token != nil)
167-
{
168-
urlRequest.addValue(_token!, forHTTPHeaderField: "authorization")
150+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
151+
urlRequest.httpMethod = HttpMethod.post
152+
urlRequest.httpBody = requestBody
153+
urlRequest.addValue("application/json", forHTTPHeaderField: HttpHeaderFields.contentType)
154+
155+
performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in
156+
completionHandler(result)
157+
}
158+
}
159+
160+
// MARK: - PUT Api
161+
private func putData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
162+
{
163+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
164+
urlRequest.httpMethod = HttpMethod.put
165+
166+
performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in
167+
completionHandler(result)
168+
}
169+
}
170+
171+
// MARK: - DELETE Api
172+
private func deleteData<T:Decodable>(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result<T?, NetworkError>)-> Void)
173+
{
174+
var urlRequest = createUrlRequest(requestUrl: requestUrl)
175+
urlRequest.httpMethod = HttpMethod.delete
176+
177+
performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in
178+
completionHandler(result)
169179
}
170-
171-
return urlRequest
172180
}
173181
}

HttpUtilityTests/IntegrationTests/HttpUtilityIntegrationTests.swift

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ class HttpUtilityIntegrationTests: XCTestCase {
2020
let requestUrl = URL(string: "http://demo0333988.mockable.io/Employees")
2121
let expectation = XCTestExpectation(description: "Data received from server")
2222

23-
// ACT
24-
_utility.getData(requestUrl: requestUrl!, resultType: Employees.self) { (response) in
25-
23+
_utility.request(requestUrl: requestUrl!, method: .get, resultType: Employees.self) { (response) in
2624
switch response
2725
{
2826
case .success(let employee):
@@ -50,8 +48,9 @@ class HttpUtilityIntegrationTests: XCTestCase {
5048
let registerUserRequest = RegisterUserRequest(firstName: "code", lastName: "cat15", email: "[email protected]", password: "1234")
5149
let registerUserBody = try! JSONEncoder().encode(registerUserRequest)
5250
let expectation = XCTestExpectation(description: "Data received from server")
51+
5352
// ACT
54-
_utility.postData(requestUrl: requestUrl!, requestBody: registerUserBody, resultType: RegisterResponse.self) { (response) in
53+
_utility.request(requestUrl: requestUrl!, method: .post, requestBody: registerUserBody, resultType: RegisterResponse.self) { (response) in
5554
switch response
5655
{
5756
case .success(let registerResponse):
@@ -78,7 +77,7 @@ class HttpUtilityIntegrationTests: XCTestCase {
7877
let requestUrl = request.convertToQueryStringUrl(urlString:"https://api-dev-scus-demo.azurewebsites.net/api/Product/GetSmartPhone")
7978

8079
// ACT
81-
_utility.getData(requestUrl: requestUrl!, resultType: PhoneResponse.self) { (response) in
80+
_utility.request(requestUrl: requestUrl!, method: .get, resultType: PhoneResponse.self) { (response) in
8281

8382
switch response
8483
{
@@ -99,4 +98,56 @@ class HttpUtilityIntegrationTests: XCTestCase {
9998

10099
wait(for: [expectation], timeout: 10.0)
101100
}
101+
102+
func test_putService_Returns_Success()
103+
{
104+
// ARRANGE
105+
let expectation = XCTestExpectation(description: "Data received from server")
106+
let requestUrl = URL(string: "https://httpbin.org/put")
107+
108+
// ACT
109+
_utility.request(requestUrl: requestUrl!, method: .put, resultType: Response.self) { (response) in
110+
111+
switch response
112+
{
113+
case .success(let serviceResponse):
114+
115+
// ASSERT
116+
XCTAssertNotNil(serviceResponse)
117+
118+
case .failure(let error):
119+
XCTAssertNil(error)
120+
}
121+
expectation.fulfill()
122+
123+
}
124+
125+
wait(for: [expectation], timeout: 10.0)
126+
}
127+
128+
func test_deleteService_Returns_Success()
129+
{
130+
// ARRANGE
131+
let expectation = XCTestExpectation(description: "Data received from server")
132+
let requestUrl = URL(string: "https://httpbin.org/delete")
133+
134+
// ACT
135+
_utility.request(requestUrl: requestUrl!, method: .delete, resultType: Response.self) { (response) in
136+
137+
switch response
138+
{
139+
case .success(let serviceResponse):
140+
141+
// ASSERT
142+
XCTAssertNotNil(serviceResponse)
143+
144+
case .failure(let error):
145+
XCTAssertNil(error)
146+
}
147+
expectation.fulfill()
148+
149+
}
150+
151+
wait(for: [expectation], timeout: 10.0)
152+
}
102153
}

0 commit comments

Comments
 (0)