Skip to content

Commit a79ad94

Browse files
committed
added validate
1 parent 4a1a9db commit a79ad94

File tree

5 files changed

+190
-16
lines changed

5 files changed

+190
-16
lines changed

README.md

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
77

88
## Features
99
- [x] Multiplatform
10-
- [x] You have fasttrack functions to make requests immediately by url or build the infrastructure configuration that suits you
10+
- [x] You have fast-track functions to make requests immediately by url or build the infrastructure configuration that suits you
1111
- [x] Stand alone package without any dependencies using just Apple's facilities
1212
- [x] Set up amount of attempts(retry) with **"Exponential backoff"** or **"Constant backoff"** strategy if request fails. Exponential backoff is a strategy in which you increase the delays between retries. Constant backoff is a strategy when delay between retries is a constant value
13+
- [x] Different strategies to validate Data or URLResponse
1314
- [x] Customizable for different requests schemes from classic **CRUD Rest** to what suits to you
1415
- [x] Customizable in terms of URLSession
1516
- [x] Customizable in terms of URLSessionTaskDelegate, URLSessionDelegate
@@ -36,6 +37,13 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
3637
try await Http.Delete.from(url)
3738
```
3839

40+
Fast-track functions return **(Data, URLResponse)** if you need to validate statusCode you can use Check yout different trategies **Http.Validate.Status** to validate status code
41+
42+
```swift
43+
/// - Throws: Http.Errors.status(response)
44+
public func validateStatus(_ response : URLResponse, by validate : [Http.Validate.Status]) throws
45+
```
46+
3947
## Extended track
4048

4149
## 1. Create
@@ -54,6 +62,11 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
5462
### GET with retry
5563
```swift
5664
try await http.get(path: "users", retry : 5)
65+
```
66+
67+
### GET with validate status code 200..<300
68+
```swift
69+
try await http.get(path: "users", validate: [.status(.range(200..<300))])
5770
```
5871

5972
### POST
@@ -83,16 +96,18 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
8396
### Custom request
8497

8598
```swift
86-
/// Send custom request based on the specific request instance
87-
/// - Parameters:
88-
/// - request: A URL load request that is independent of protocol or URL scheme
89-
/// - retry: ``RetryService.Strategy`` Default value .exponential with 5 retry and duration 2.0 sec
90-
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
91-
public func send<T>(
92-
with request : URLRequest,
93-
retry strategy : RetryService.Strategy,
94-
_ taskDelegate: ITaskDelegate? = nil
95-
) async throws -> Http.Response<T> where T : Decodable
99+
/// Send custom request based on the specific request instance
100+
/// - Parameters:
101+
/// - request: A URL load request that is independent of protocol or URL scheme
102+
/// - retry: ``RetryService.Strategy`` strategy Default value .exponential with 5 retry and duration 2.0
103+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set up diff combinations check out ``Http.Validate.Status``
104+
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
105+
public func send<T>(
106+
with request : URLRequest,
107+
retry strategy : RetryService.Strategy = .exponential(),
108+
_ validate : [Http.Validate] = [.status(.const(200))],
109+
_ taskDelegate: ITaskDelegate? = nil
110+
) async throws -> Http.Response<T> where T : Decodable
96111
```
97112

98113
## Retry strategy
@@ -104,7 +119,31 @@ This package uses stand alone package providing retry policy. The service create
104119
| constant | The strategy implements constant backoff |
105120
| exponential [default] | The strategy implements exponential backoff |
106121

122+
## Validate
123+
Is an array of different rules to check Data or URLResponse.
124+
Currently is implemented for status code. You can pass to the validate array different combinations to validate like for status code 200 and 202..<205
125+
126+
### Status code
127+
| type | description |
128+
| --- | --- |
129+
| const(Int) [default] 200 | Validate by exact value |
130+
| range(Range<Int>) | Validate by range |
131+
| predicate(Predicate) | Validate by predicate func if you need some specific precessing logic |
132+
133+
#### By range
134+
```swift
107135

136+
try await http.get(path: path, validate: [.status(.range(200..<300))])
137+
138+
```
139+
140+
#### By predicate
141+
```swift
142+
143+
let fn : (Int) -> Bool = { status in status == 201 }
144+
145+
try await http.get(path: path, validate: [.status(.predicate(fn))])
146+
```
108147

109148
# The concept
110149

Sources/async-http-client/proxy/http/Proxy.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ public extension Http{
3535
/// - query: An array of name-value pairs
3636
/// - headers: A dictionary containing all of the HTTP header fields for a request
3737
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
38+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set diff combinations check out ``Http.Validate.Status``
3839
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
3940
public func get<T>(
4041
path: String,
4142
query : Query? = nil,
4243
headers : Headers? = nil,
4344
retry : UInt = 1,
45+
validate : [Http.Validate] = [.status(.const(200))],
4446
taskDelegate: ITaskDelegate? = nil
4547
) async throws
4648
-> Http.Response<T> where T: Decodable
@@ -51,7 +53,7 @@ public extension Http{
5153
for: path, query: query, headers: headers)
5254
let strategy = RetryService.Strategy.exponential(retry: retry)
5355

54-
return try await send(with: request, retry: strategy, taskDelegate)
56+
return try await send(with: request, retry: strategy, validate, taskDelegate)
5557
}
5658

5759
/// POST request
@@ -60,20 +62,22 @@ public extension Http{
6062
/// - query: An array of name-value pairs
6163
/// - headers: A dictionary containing all of the HTTP header fields for a request
6264
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
65+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set diff combinations check out ``Http.Validate.Status``
6366
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
6467
public func post<T>(
6568
path: String,
6669
query : Query? = nil,
6770
headers : Headers? = nil,
6871
retry : UInt = 1,
72+
validate : [Http.Validate] = [.status(.const(200))],
6973
taskDelegate: ITaskDelegate? = nil
7074
) async throws
7175
-> Http.Response<T> where T: Decodable
7276
{
7377
let request = try buildURLRequest(config.baseURL,
7478
for: path, method: .post, query: query, headers: headers)
7579
let strategy = RetryService.Strategy.exponential(retry: retry)
76-
return try await send(with: request, retry: strategy, taskDelegate)
80+
return try await send(with: request, retry: strategy, validate, taskDelegate)
7781
}
7882

7983
/// POST request
@@ -83,13 +87,15 @@ public extension Http{
8387
/// - query: An array of name-value pairs
8488
/// - headers: A dictionary containing all of the HTTP header fields for a request
8589
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
90+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set diff combinations check out ``Http.Validate.Status``
8691
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
8792
public func post<T, V : Encodable>(
8893
path: String,
8994
body : V? = nil,
9095
query : Query? = nil,
9196
headers : Headers? = nil,
9297
retry : UInt = 1,
98+
validate : [Http.Validate] = [.status(.const(200))],
9399
taskDelegate: ITaskDelegate? = nil
94100
) async throws
95101
-> Http.Response<T> where T: Decodable
@@ -101,7 +107,7 @@ public extension Http{
101107
let strategy = RetryService.Strategy.exponential(retry: retry)
102108

103109

104-
return try await send(with: request, retry: strategy, taskDelegate)
110+
return try await send(with: request, retry: strategy, validate, taskDelegate)
105111
}
106112

107113
/// PUT request
@@ -111,13 +117,15 @@ public extension Http{
111117
/// - query: An array of name-value pairs
112118
/// - headers: A dictionary containing all of the HTTP header fields for a request
113119
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
120+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set diff combinations check out ``Http.Validate.Status``
114121
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
115122
public func put<T, V : Encodable>(
116123
path: String,
117124
body : V? = nil,
118125
query : Query? = nil,
119126
headers : Headers? = nil,
120127
retry : UInt = 1,
128+
validate : [Http.Validate] = [.status(.const(200))],
121129
taskDelegate: ITaskDelegate? = nil
122130
) async throws
123131
-> Http.Response<T> where T: Decodable
@@ -133,7 +141,7 @@ public extension Http{
133141
request.setValue(content, forHTTPHeaderField: "Content-Type") // for PUT
134142
}
135143

136-
return try await send(with: request, retry: strategy, taskDelegate)
144+
return try await send(with: request, retry: strategy, validate, taskDelegate)
137145
}
138146

139147
/// DELETE request
@@ -142,12 +150,14 @@ public extension Http{
142150
/// - query: An array of name-value pairs
143151
/// - headers: A dictionary containing all of the HTTP header fields for a request
144152
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
153+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set diff combinations check out ``Http.Validate.Status``
145154
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
146155
public func delete<T>(
147156
path: String,
148157
query : Query? = nil,
149158
headers : Headers? = nil,
150159
retry : UInt = 1,
160+
validate : [Http.Validate] = [.status(.range(200..<300))],
151161
taskDelegate: ITaskDelegate? = nil
152162
) async throws
153163
-> Http.Response<T> where T: Decodable
@@ -156,17 +166,19 @@ public extension Http{
156166
config.baseURL,
157167
for: path, method: .delete, query: query, headers: headers)
158168
let strategy = RetryService.Strategy.exponential(retry: retry)
159-
return try await send(with: request, retry: strategy, taskDelegate)
169+
return try await send(with: request, retry: strategy, validate, taskDelegate)
160170
}
161171

162172
/// Send custom request based on the specific request instance
163173
/// - Parameters:
164174
/// - request: A URL load request that is independent of protocol or URL scheme
165175
/// - retry: ``RetryService.Strategy`` strategy Default value .exponential with 5 retry and duration 2.0
176+
/// - validate: Set of custom validate fun ``Http.Validate`` For status code like an example Default value to validate statusCode == 200 You can set up diff combinations check out ``Http.Validate.Status``
166177
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
167178
public func send<T>(
168179
with request : URLRequest,
169180
retry strategy : RetryService.Strategy = .exponential(),
181+
_ validate : [Http.Validate] = [.status(.const(200))],
170182
_ taskDelegate: ITaskDelegate? = nil
171183
) async throws -> Http.Response<T> where T : Decodable
172184
{
@@ -180,6 +192,8 @@ public extension Http{
180192
config.getSession
181193
)
182194

195+
try validateStatus(response, by : validate.filterStatus())
196+
183197
let value: T = try reader.read(data: data)
184198

185199
return .init(value: value, data: data, response, request)
@@ -197,3 +211,4 @@ fileprivate func hasNotContentType(_ session : URLSession,_ request : URLRequest
197211
request.value(forHTTPHeaderField: "Content-Type") == nil &&
198212
session.configuration.httpAdditionalHeaders?["Content-Type"] == nil
199213
}
214+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// Error.swift
3+
//
4+
//
5+
// Created by Igor on 13.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
extension Http{
11+
12+
enum Errors : Error{
13+
case status(Int?, URLResponse)
14+
}
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Validate.swift
3+
//
4+
//
5+
// Created by Igor on 13.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
public extension Http{
11+
12+
/// Set of validate cases for Http client
13+
enum Validate {
14+
/// Set of validate cases for URLResponse status
15+
case status(Status)
16+
}
17+
}
18+
19+
internal extension Http.Validate{
20+
21+
/// Check if validate status
22+
var isStatus : Status{
23+
switch(self){
24+
case .status(let rule) : return rule
25+
}
26+
}
27+
}
28+
29+
30+
internal extension Collection where Element == Http.Validate{
31+
32+
/// Filter status validate fn
33+
/// - Returns: Set of fn to validate status
34+
func filterStatus() -> [Http.Validate.Status] {
35+
map{ $0.isStatus }
36+
}
37+
}
38+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Status.swift
3+
//
4+
//
5+
// Created by Igor on 13.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
public extension Http.Validate{
11+
12+
/// Set of validate cases for Response status code
13+
enum Status{
14+
15+
public typealias Predicate = (Int) -> Bool
16+
17+
/// Validate by exact value
18+
case const(Int)
19+
20+
/// Validate by range
21+
case range(Range<Int>)
22+
23+
/// Validate by predicate func if you need some specific logic
24+
case predicate(Predicate)
25+
26+
}
27+
}
28+
29+
public extension Http.Validate.Status{
30+
31+
/// Validate status
32+
/// - Parameter data: Response
33+
func validate(_ data : URLResponse) throws{
34+
35+
guard let status = (data as? HTTPURLResponse)?.statusCode else{ return try err(nil, data) }
36+
37+
switch(self){
38+
case .const(let value):
39+
if value != status { try err(status, data) }
40+
case .range(let value) :
41+
if !value.contains(status) { try err(status, data) }
42+
case .predicate(let fn):
43+
if !fn(status) { try err(status, data) }
44+
}
45+
}
46+
}
47+
48+
// MARK: - Public
49+
50+
51+
/// Validate status
52+
/// - Parameters:
53+
/// - response: URLResponse
54+
/// - validate: Set of func to validate status code
55+
/// - Throws: Http.Errors.status(response)
56+
public func validateStatus(_ response : URLResponse, by validate : [Http.Validate.Status]) throws{
57+
58+
try validate.forEach{
59+
try $0.validate(response)
60+
}
61+
}
62+
63+
// MARK: - File private
64+
65+
fileprivate func err(_ status: Int?, _ response : URLResponse) throws{
66+
throw Http.Errors.status(status, response)
67+
}

0 commit comments

Comments
 (0)