Skip to content

Commit 1645c17

Browse files
committed
Adds ResourceWithError
1 parent eda31db commit 1645c17

8 files changed

Lines changed: 548 additions & 1 deletion
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
//
2+
// Copyright (C) 2021 DB Systel GmbH.
3+
// DB Systel GmbH; Jürgen-Ponto-Platz 1; D-60329 Frankfurt am Main; Germany; http://www.dbsystel.de/
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a
6+
// copy of this software and associated documentation files (the "Software"),
7+
// to deal in the Software without restriction, including without limitation
8+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
9+
// and/or sell copies of the Software, and to permit persons to whom the
10+
// Software is furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18+
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21+
// DEALINGS IN THE SOFTWARE.
22+
//
23+
24+
import Foundation
25+
26+
extension NetworkService {
27+
28+
/**
29+
Fetches a resource asynchronously from remote location. Execution of the requests starts immediately.
30+
Execution happens on no specific queue. It dependes on the network access which queue is used.
31+
Once execution is finished either the completion block or the error block gets called.
32+
You decide on which queue these blocks get executed.
33+
34+
**Example**:
35+
```swift
36+
let networkService: NetworkService = //
37+
let resource: ResourceWithError<String, CustomError> = //
38+
39+
networkService.request(queue: .main, resource: resource, onCompletionWithResponse: { htmlText, response in
40+
print(htmlText, response)
41+
}, onError: { error in
42+
// Handle errors
43+
})
44+
```
45+
46+
- parameter queue: The `DispatchQueue` to execute the completion and error block on.
47+
- parameter resource: The resource you want to fetch.
48+
- parameter onCompletionWithResponse: Callback which gets called when fetching and transforming into model succeeds.
49+
- parameter onError: Callback which gets called with an custom error.
50+
51+
- returns: a running network task
52+
*/
53+
@discardableResult
54+
public func request<Result, E: Error>(
55+
queue: DispatchQueue,
56+
resource: ResourceWithError<Result, E>,
57+
onCompletionWithResponse: @escaping (Result, HTTPURLResponse) -> Void,
58+
onError: @escaping (E) -> Void
59+
) -> NetworkTask {
60+
let resourceWithoutError = Resource(request: resource.request, parse: resource.parse)
61+
return request(queue: queue, resource: resourceWithoutError, onCompletionWithResponse: onCompletionWithResponse) { networkError in
62+
onError(resource.mapError(networkError))
63+
}
64+
}
65+
66+
/**
67+
Fetches a resource asynchronously from remote location. Execution of the requests starts immediately.
68+
Execution happens on no specific queue. It dependes on the network access which queue is used.
69+
Once execution is finished either the completion block or the error block gets called.
70+
These blocks are called on the main queue.
71+
72+
**Example**:
73+
```swift
74+
let networkService: NetworkService = //
75+
let resource: ResourceWithError<String, CustomError> = //
76+
77+
networkService.request(resource, onCompletion: { htmlText in
78+
print(htmlText)
79+
}, onError: { error in
80+
// Handle errors
81+
})
82+
```
83+
84+
- parameter resource: The resource you want to fetch.
85+
- parameter onComplition: Callback which gets called when fetching and transforming into model succeeds.
86+
- parameter onError: Callback which gets called with an custom error.
87+
88+
- returns: a running network task
89+
*/
90+
@discardableResult
91+
public func request<Result, E: Error>(
92+
_ resource: ResourceWithError<Result, E>,
93+
onCompletion: @escaping (Result) -> Void,
94+
onError: @escaping (E) -> Void
95+
) -> NetworkTask {
96+
return request(
97+
queue: .main,
98+
resource: resource,
99+
onCompletionWithResponse: { model, _ in onCompletion(model) },
100+
onError: onError
101+
)
102+
}
103+
104+
/**
105+
Fetches a resource asynchronously from remote location. Execution of the requests starts immediately.
106+
Execution happens on no specific queue. It dependes on the network access which queue is used.
107+
Once execution is finished either the completion block or the error block gets called.
108+
You decide on which queue these blocks get executed.
109+
110+
**Example**:
111+
```swift
112+
let networkService: NetworkService = //
113+
let resource: ResourceWithError<String, CustomError> = //
114+
115+
networkService.request(queue: .main, resource: resource, onCompletionWithResponse: { htmlText, response in
116+
print(htmlText, response)
117+
}, onError: { error in
118+
// Handle errors
119+
})
120+
```
121+
122+
- parameter queue: The `DispatchQueue` to execute the completion and error block on.
123+
- parameter resource: The resource you want to fetch.
124+
- parameter onCompletionWithResponse: Callback which gets called when fetching and transforming into model succeeds.
125+
- parameter onError: Callback which gets called with an custom error.
126+
127+
- returns: a running network task
128+
*/
129+
@discardableResult
130+
func request<Result, E: Error>(
131+
queue: DispatchQueue = .main,
132+
resource: ResourceWithError<Result, E>,
133+
onCompletionWithResponse: @escaping (Swift.Result<(Result, HTTPURLResponse), E>) -> Void
134+
) -> NetworkTask {
135+
return request(
136+
queue: queue,
137+
resource: resource,
138+
onCompletionWithResponse: { result, response in
139+
onCompletionWithResponse(.success((result, response)))
140+
}, onError: { error in
141+
onCompletionWithResponse(.failure(error))
142+
}
143+
)
144+
}
145+
146+
/**
147+
Fetches a resource asynchronously from remote location. Execution of the requests starts immediately.
148+
Execution happens on no specific queue. It dependes on the network access which queue is used.
149+
Once execution is finished either the completion block or the error block gets called.
150+
These blocks are called on the main queue.
151+
152+
**Example**:
153+
```swift
154+
let networkService: NetworkService = //
155+
let resource: ResourceWithError<String, CustomError> = //
156+
157+
networkService.request(resource, onCompletion: { htmlText in
158+
print(htmlText)
159+
}, onError: { error in
160+
// Handle errors
161+
})
162+
```
163+
164+
- parameter resource: The resource you want to fetch.
165+
- parameter onComplition: Callback which gets called when fetching and transforming into model succeeds.
166+
- parameter onError: Callback which gets called with an custom error.
167+
168+
- returns: a running network task
169+
*/
170+
@discardableResult
171+
public func request<Result, E: Error>(
172+
_ resource: ResourceWithError<Result, E>,
173+
onCompletion: @escaping (Swift.Result<Result, E>) -> Void
174+
) -> NetworkTask {
175+
return request(
176+
queue: .main,
177+
resource: resource,
178+
onCompletionWithResponse: { model, _ in onCompletion(.success(model)) },
179+
onError: { onCompletion(.failure($0))}
180+
)
181+
}
182+
183+
/**
184+
Fetches a resource asynchronously from remote location. Execution of the requests starts immediately.
185+
Execution happens on no specific queue. It dependes on the network access which queue is used.
186+
Once execution is finished either the completion block or the error block gets called.
187+
These blocks are called on the main queue.
188+
189+
**Example**:
190+
```swift
191+
let networkService: NetworkService = //
192+
let resource: Resource<String> = //
193+
194+
networkService.request(resource, onCompletionWithResponse: { htmlText, httpResponse in
195+
print(htmlText, httpResponse)
196+
}, onError: { error in
197+
// Handle errors
198+
})
199+
```
200+
201+
- parameter resource: The resource you want to fetch.
202+
- parameter onCompletion: Callback which gets called when fetching and transforming into model succeeds.
203+
- parameter onError: Callback which gets called when fetching or transforming fails.
204+
205+
- returns: a running network task
206+
*/
207+
@discardableResult
208+
func request<Result, E: Error>(_ resource: ResourceWithError<Result, E>, onCompletionWithResponse: @escaping (Result, HTTPURLResponse) -> Void,
209+
onError: @escaping (E) -> Void) -> NetworkTask {
210+
return request(queue: .main, resource: resource, onCompletionWithResponse: onCompletionWithResponse, onError: onError)
211+
}
212+
}

Source/Resource+Decodable.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,17 @@ extension Resource where Model: Decodable {
3535
self.init(request: request, parse: { try decoder.decode(Model.self, from: $0) })
3636
}
3737
}
38+
39+
extension ResourceWithError where Model: Decodable {
40+
41+
/// Creates an instace of Resource where the result type is `Decodable` and
42+
/// can be decoded with the given decoder
43+
///
44+
/// - Parameters:
45+
/// - request: The request to get the remote data payload
46+
/// - decoder: a decoder which can decode the payload into the model type
47+
/// - mapError: a closure which maps to Error
48+
public init(request: URLRequest, decoder: JSONDecoder, mapError: @escaping (_ networkError: NetworkError) -> E) {
49+
self.init(request: request, parse: { try decoder.decode(Model.self, from: $0) }, mapError: mapError)
50+
}
51+
}

Source/Resource+Map.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,22 @@ extension Resource {
3333
})
3434
}
3535
}
36+
37+
extension ResourceWithError {
38+
39+
/// Maps a resource result to a different resource. This is useful when you have result of R which contains T and your API request a resource of T,
40+
///
41+
/// Error parsing is not changed
42+
///
43+
/// - Parameter transform: transforms the original result of the resource
44+
/// - Returns: the transformed resource
45+
public func map<T>(transform: @escaping (Model) throws -> T) -> ResourceWithError<T, E> {
46+
return ResourceWithError<T, E>(
47+
request: request,
48+
parse: { data in
49+
return try transform(try self.parse(data))
50+
},
51+
mapError: mapError
52+
)
53+
}
54+
}

Source/Resource+Void.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,15 @@ public extension Resource where Model == Void {
1717
self.init(request: request, parse: { _ in })
1818
}
1919
}
20+
21+
extension ResourceWithError where Model == Void {
22+
23+
/// Creates an instace of Resource where the result type is `Void`
24+
///
25+
/// - Parameters:
26+
/// - request: The request to get the remote data payload
27+
/// - mapError: a closure which maps to Error
28+
public init(request: URLRequest, mapError: @escaping (_ networkError: NetworkError) -> E) {
29+
self.init(request: request, parse: { _ in }, mapError: mapError)
30+
}
31+
}

Source/ResourceWithError.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Copyright (C) 2021 DB Systel GmbH.
3+
// DB Systel GmbH; Jürgen-Ponto-Platz 1; D-60329 Frankfurt am Main; Germany; http://www.dbsystel.de/
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a
6+
// copy of this software and associated documentation files (the "Software"),
7+
// to deal in the Software without restriction, including without limitation
8+
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
9+
// and/or sell copies of the Software, and to permit persons to whom the
10+
// Software is furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18+
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21+
// DEALINGS IN THE SOFTWARE.
22+
//
23+
24+
import Foundation
25+
26+
/**
27+
`ResourceWithError` describes a remote resource of generic type and generic error.
28+
The type can be fetched via HTTP(S) and parsed into the coresponding model object.
29+
30+
**Example**:
31+
```swift
32+
let request: URLRequest = //
33+
let resource: ResourceWithError<String?, CustomError> = Resource(request: request, parse: { data in
34+
String(data: data, encoding: .utf8)
35+
}, mapError: { networkError in
36+
return CustomError(networkError)
37+
})
38+
```
39+
*/
40+
public struct ResourceWithError<Model, E: Error> {
41+
/// The request to fetch the resource remote payload
42+
public let request: URLRequest
43+
44+
/// Parses data into given model.
45+
public let parse: (_ data: Data) throws -> Model
46+
public let mapError: (_ networkError: NetworkError) -> E
47+
48+
/// Creates a type safe resource, which can be used to fetch it with NetworkService
49+
///
50+
/// - Parameters:
51+
/// - request: The request to get the remote data payload
52+
/// - parse: Parses data fetched with the request into given Model
53+
54+
public init(request: URLRequest, parse: @escaping (Data) throws -> Model, mapError: @escaping (_ networkError: NetworkError) -> E) {
55+
self.request = request
56+
self.parse = parse
57+
self.mapError = mapError
58+
}
59+
}

0 commit comments

Comments
 (0)