Alamofire retry request - reactive way - swift

I was looking at those two:
http://sapandiwakar.in/refresh-oauth-tokens-using-moya-rxswift/
Using retryWhen to update tokens based on http error code
And trying to create similiar thing, but without Moya, using Alamofire + RxSwift.
First of all is obviously where should I stick this, since my implementation is divided into a couple smaller parts.
First of all I have my custom method for generating reactive requests:
static func rx_request<T>(requestConvertible: URLRequestConvertible, completion: (Request) -> Observable<T> ) -> Observable<T> {
let manager: Manager = Manager.sharedInstance
return manager
.rx_request { manager -> Request in
return Alamofire.request(requestConvertible)
}
.flatMap { request -> Observable<T> in
return completion(request)
}
.shareReplay(1)
}
Which is later used by specific Requests convenience classes. For example my UserRequests has this private extension to extract some common code from it's methods:
private extension Request {
func rx_userRequest() -> Observable<User> {
return self
.validate()
.rx_responseJSON()
.flatMap{ (request, json) -> Observable<User> in
guard
let dict = json as? [ String: AnyObject ],
let parsedUser: User = try? Unbox(dict) else {
return Observable.error(RequestError.ParsingError)
}
return Observable.just(parsedUser)
}
.rx_storeCredentials()
}
}
Because of how things looks like I wonder whare's the right place to put a retry method and also how to implement it? Because depending on the location I can get different input arguments.

The retry code has to go after the first try, which is rx_responseJSON so the way you have things setup now, it must go between that and the flatMap after it.

Related

Removing Swift RxAlamofire dependency

I'm trying to remove my dependency on RxAlamofire.
I currently have this function:
func requestData(_ urlRequest: URLRequestConvertible) -> Observable<(HTTPURLResponse, Data)> {
RxAlamofire.request(urlRequest).responseData()
}
How can I refactor this and use Alamofire directly to build and return an RxSwift Observable?
I suggest you look at the way the library wraps URLRequest to get an idea on how to do it...
Below is an abbreviated example from the library. In essence, you need to use Observable.create, make the network call passing in a closure that knows how to use the observer that create gives you.
Make sure you send a completed when done and make sure the disposable knows how to cancel the request.
Your Base will be something in Alamofire (I don't use Alamofire so I'm not sure what that might be.)
extension Reactive where Base: URLSession {
/**
Observable sequence of responses for URL request.
Performing of request starts after observer is subscribed and not after invoking this method.
**URL requests will be performed per subscribed observer.**
Any error during fetching of the response will cause observed sequence to terminate with error.
- parameter request: URL request.
- returns: Observable sequence of URL responses.
*/
public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable.create { observer in
let task = self.base.dataTask(with: request) { data, response, error in
guard let response = response, let data = data else {
observer.on(.error(error ?? RxCocoaURLError.unknown))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}
observer.on(.next((httpResponse, data)))
observer.on(.completed)
}
task.resume()
return Disposables.create(with: task.cancel)
}
}
}

Swift Combine: Cannot refactor repetitive code

My API returns this format, where data can contain all kinds of responses.
{
status: // http status
error?: // error handle
data?: // your response data
meta?: // meta data, eg. pagination
debug?: // debuging infos
}
I have made a Codable Response type with a generic for the optional data, of which we do not know the type.
struct MyResponse<T: Codable>: Codable {
let status: Int
let error: String?
let data: T?
let meta: Paging?
let debug: String?
}
I am now trying to write API convenience methods as concisely as possible. So I have a function to return a generic publisher that I can use for all these responses, i.e. one that pre-parses the response and catches any errors upfront.
First, I get a dataTaskPublisher that processes the parameter inputs, if any. Endpoint is just a convenience String enum for my endpoints, Method is similar. MyRequest returns a URLRequest with some necessary headers etc.
Notice the way I define the parameters: params: [String:T]. This is standard JSON so it could be strings, numbers etc.
It seems this T is the problem somehow..
static fileprivate func publisher<T: Encodable>(
_ path: Endpoint,
method: Method,
params: [String:T] = [:]) throws
-> URLSession.DataTaskPublisher
{
let url = API.baseURL.appendingPathComponent(path.rawValue)
var request = API.MyRequest(url: url)
if method == .POST && params.count > 0 {
request.httpMethod = method.rawValue
do {
let data = try JSONEncoder().encode(params)
request.httpBody = data
return URLSession.shared.dataTaskPublisher(for: request)
}
catch let err {
throw MyError.encoding(description: String(describing: err))
}
}
return URLSession.shared.dataTaskPublisher(for: request)
}
Next, I am parsing the response.
static func myPublisher<T: Encodable, R: Decodable>(
_ path: Endpoint,
method: Method = .GET,
params: [String:T] = [:])
-> AnyPublisher<MyResponse<R>, MyError>
{
do {
return try publisher(path, method: method, params: params)
.map(\.data)
.mapError { MyError.network(description: "\($0)")}
.decode(type: MyResponse<R>.self, decoder: self.agent.decoder)
.mapError { MyError.encoding(description: "\($0)")} //(2)
.tryMap {
if $0.status > 204 {
throw MyError.network(description: "\($0.status): \($0.error!)")
}
else {
return $0 // returns a MyResponse
}
}
.mapError { $0 as! MyError }
//(1)
.eraseToAnyPublisher()
}
catch let err {
return Fail<MyResponse<R>,MyError>(error: err as? MyError ??
MyError.undefined(description: "\(err)"))
.eraseToAnyPublisher()
}
}
Now I can write an endpoint method easily. Here are two examples.
static func documents() -> AnyPublisher<[Document], MyError> {
return myPublisher(.documents)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<[Document], MyError>
}
and
static func user() -> AnyPublisher<User, MyError> {
return myPublisher(.user)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() as AnyPublisher<User, MyError>
}
All this is working well. Please note that each time, I have to specify my exact return type twice. I think I can live with that.
I should be able to simplify this so that I do not have to repeat the same three operators (map, mapError, receive) in exactly the same way each time.
But when I insert .map(\.data!) at the location //(1) above I get the error Generic parameter T could not be inferred. at the location //(2).
This is really confusing. Why does the generic type in the input parameters play any role here? This must be related to the call to the .decode operator just above, where the generic in question is called R, not T.
Can you explain this? How can I refactor these operators upstream?
This code has a number of small problems. You're right that one is [String: T]. That means that for a given set of parameters, all the values must be of the same type. That's not "JSON." This will accept a [String: String] or a [String: Int], but you can't have both Int and String values in the same dictionary if you do it this way. And it will also accept [String: Document], and it doesn't seem like you really want that.
I'd recommend switching this to just Encodable, which would allow you to pass structs if that were convenient, or Dictionaries if that were convenient:
func publisher<Params: Encodable>(
_ path: Endpoint,
method: Method,
params: Params?) throws
-> URLSession.DataTaskPublisher
func myPublisher<Params: Encodable, R: Decodable>(
_ path: Endpoint,
method: Method = .GET,
params: Params?)
-> AnyPublisher<MyResponse<R>, MyError>
Then modify your params.count to check for nil instead.
Note that I didn't make params = nil a default parameter. That's because this would recreate a second problem you have. T (and Params) can't be inferred in the default case. For = [:], what is T? Swift has to know, even though it's empty. So instead of a default, you use an overload:
func myPublisher<R: Decodable>(
_ path: Endpoint,
method: Method = .GET)
-> AnyPublisher<MyResponse<R>, MyError> {
let params: String? = nil // This should be `Never?`, see https://twitter.com/cocoaphony/status/1184470123899478017
return myPublisher(path, method: method, params: params)
}
Now, when you don't pass any parameters, Params automatically becomes String.
So now your code is fine, and you don't need the as at the end
func documents() -> AnyPublisher<[Document], MyError> {
myPublisher(.documents)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() // <== Removed `as ...`
}
Now, that .map(\.data!) makes me sad. If you get back corrupt data from the server, the app will crash. There are lots of good reasons to crash apps; bad server data is never one of them. But fixing that isn't really related to this question (and is a little bit complicated because Failure types other than Error make things hard currently), so I'll leave it for now. My general recommendation is to use Error as your Failure type, and allow unexpected errors to just bubble up rather than wrapping them in an .undefined case. If you need some catch-all "other" anyway, you might as well do that with types ("is") rather than an extra enum case (which just moves the "is" to a switch). At the very least, I would move the Error->MyError mapping as late as possible, which will make handling this much easier.
One more tweak to make later things a little more general, I suspect MyResponse only needs to be Decodable, not Encodable (the rest of this works either way, but it makes it a little more flexible):
struct MyResponse<T: Decodable>: Decodable { ... }
And to your original question of how to make this reusable, you can now pull out a generic function:
func fetch<DataType, Params>(_: DataType.Type,
from endpoint: Endpoint,
method: Method = .GET,
params: Params?) -> AnyPublisher<DataType, MyError>
where DataType: Decodable, Params: Encodable
{
myPublisher(endpoint, method: method, params: params)
.map(\.data!)
.mapError { MyError.network(description: $0.errorDescription) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
// Overload to handle no parameters
func fetch<DataType>(_ dataType: DataType.Type,
from endpoint: Endpoint,
method: Method = .GET) -> AnyPublisher<DataType, MyError>
where DataType: Decodable
{
fetch(dataType, from: endpoint, method: method, params: nil as String?)
}
func documents() -> AnyPublisher<[Document], MyError> {
fetch([Document].self, from: .documents)
}

Create a Swift HTTP mock with alternate data

I have a mocked HTTPManager, and I want it to either return a userIDResonse or a tokenResponse.
To be able to do this I made the mock conform to a protocol to allow this to be set within the test.
let userIDResponse = """
{\"user_id\":\"5a7ab957a225856b38f49bb4\"}
"""
let tokenResponse = """
{\"access_token\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjczMUE3OUEyMjY3QjY4Q0EwNTc5QjYzRjdFMkY0QjlBQkZFMENEMTUiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJjeHA1b2laN2FNb0ZlYllfZmk5TG1yX2d6UlUifQ.eyJuYmYiOjE1MTI5NjU3NTgsImV4cCI6MTUxMjk2OTM1OCwiaXNzIjoiaHR0cHM6Ly9pZG0uYWxwaGFub3ZhLnNnIiwiYXVkIjpbImh0dHBzOi8vaWRtLmFscGhhbm92YS5zZy9yZXNvdXJjZXMiLCJhcGkyIl0sImNsaWVudF9pZCI6ImNhcGFwb3QtbmciLCJzdWIiOiI1YTFjMWU5MjY0MjUzYjFlMWU2N2ZhZDIiLCJhdXRoX3RpbWUiOjE1MTI5NjU3NTgsImlkcCI6ImFscGhhbm92YSIsImZpcnN0X25hbWUiOiJTdGV2ZW4iLCJsYXN0X25hbWUiOiJDdXJ0aXMiLCJuYW1lIjoiU3RldmVuIEN1cnRpcyIsImVtYWlsIjoic3RldmVuQGFscGhhbm92YS5zZyIsInNjb3BlIjpbImNhcGFwb3QucHJvZmlsZSIsImVtYWlsIiwib3BlbmlkIiwiYXBpMi5yZWFkX29ubHkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicGFzc3dvcmQiXX0.q4-SF5KBVSwN4bFhcQ88icR9X2jzz_JH2K4EpDgS-oZjjppNruckxfTjauVqcwG8zPR0eGzx5CBXiAfMeg9akShWajqBZ9rkCsqjXw6Ef74J9cTBDhxTEUL0v7P0zm_fVNOutM_UJQ-DiQr2gAO0mfAxMhOiQ_uXlKoM2RYGKjfMkH6Ym7kBjtRAhho8pPVmtQiBmVFI5OUVXNU3rPVgB7sx-I1LZmUZBZoy7T4s14TAuE4yiUyTBgO5joyRsZtMdFybna8CRK_ylS3WC6wOBNm74O9IrZlbsiradtLzMG-9E8AnjbvH4RYR68H2xpt562PfnGD_VC9NXFQ7iRrRMw\"}
"""
Used by the Mock
protocol HTTPManagerMockProtocol {
func setResponse(response: String.UTF8View)
}
typealias HTTPMock = HTTPManagerProtocol & HTTPManagerMockProtocol
class HTTPManagerMock: HTTPMock {
var data = Data(userIDResponse.utf8)
func setResponse(response: String.UTF8View) {
data = Data(response)
}
func get(urlString: String, parameters: [String : String], completionBlock: #escaping (Result<Data, Error>) -> Void) {
completionBlock(.success(data))
}
}
So then in my test I have to set the reponse:
let httpMock = HTTPManagerMock()
httpMock.setResponse(response: tokenResponse.utf8)
sut = Login(serverString: "serverURL", headers: [:], httpManager: httpMock )
In some ways this seems ok, however it means I cannot use the setup function in my tests which results in repeated code within my test classes.
Which approach can mean I can have a mock with different output without generating extra test code?
Make a parameterized helper method to create your System Under Test.
private func makeLogin(response: String) -> Login {
let httpMock = HTTPManagerMock()
httpMock.setResponse(response: response.utf8)
return Login(serverString: "serverURL", headers: [:], httpManager: httpMock)
}
That way, you can vary the response across different tests. And if you have tests where you don't really care about the response and want to provide dummy data, that can be a default value in the helper.

Concise method of updating a model to match a request body with Swift?

Given a controller method which accepts a request body which conforms to some or all of the properties in an entity in Vapor, is there a means of updating the entity without manually assigning all of it's properties? Currently, I'm having to do this:
func update(_ req: Request) throws -> Future<Mission> {
let mission = try req.parameters.next(Mission.self)
let content = try req.content.decode(Mission.self)
return flatMap(to: Mission.self, mission, content) { (mission, content) in
mission.propertyA = content.propertyA
mission.propB = content.propB
mission.propC = content.propC
return mission.save(on: req)
}
}
This isn't very scalable as it requires me to manually assign each property. What I'm looking for is something like this:
func update(_ req: Request) throws -> Future<Mission> {
let mission = try req.parameters.next(Mission.self)
let content = try req.content.decode(Mission.self)
return mission.save(on: content)
}
However this yields the error Argument type 'EventLoopFuture<Mission>' does not conform to expected type 'DatabaseConnectable'.
What is a good solution here?
With Submissions you should be able to do:
func create(req: Request) throws -> Future<Either<Mission, SubmissionValidationError>> {
return try req.content.decode(Mission.Submission.self)
.updateValid(on: req)
.save(on: req)
.promoteErrors()
}
It takes some setting up but it's flexible and allows you to validate your input. The promoteErrors function + Either in the result help create useful error response but you could do without them.
That error that you're receiving is because you're trying to save(on: content), you need to save on the request:
return mission.save(on: req)
That being said, what you really want is this:
func update(_ req: Request) throws -> Future<Mission> {
let updatedMission = try req.content.decode(Mission.self)
return updatedMission.update(on: req)
}
This decodes a Mission object that is in the request body and then updates the Mission with the corresponding id in the database. So make sure when you're sending Mission JSON in the body that it has an id.

Recursive/looping NSURLSession async completion handlers

The API I use requires multiple requests to get search results. It's designed this way because searches can take a long time (> 5min). The initial response comes back immediately with metadata about the search, and that metadata is used in follow up requests until the search is complete. I do not control the API.
1st request is a POST to https://api.com/sessions/search/
The response to this request contains a cookie and metadata about the search. The important fields in this response are the search_cookie (a String) and search_completed_pct (an Int)
2nd request is a POST to https://api.com/sessions/results/ with the search_cookie appended to the URL. eg https://api.com/sessions/results/c601eeb7872b7+0
The response to the 2nd request will contain either:
The search results if the query has completed (aka search_completed_pct == 100)
Metadata about the progress of search, search_completed_pct is the progress of the search and will be between 0 and 100.
If the search is not complete, I want to make a request every 5 seconds until it's complete (aka search_completed_pct == 100)
I've found numerous posts here that are similar, many use Dispatch Groups and for loops, but that approach did not work for me. I've tried a while loop and had issues with variable scoping. Dispatch groups also didn't work for me. This smelled like the wrong way to go, but I'm not sure.
I'm looking for the proper design to make these recursive calls. Should I use delegates or are closures + loop the way to go? I've hit a wall and need some help.
The code below is the general idea of what I've tried (edited for clarity. No dispatch_groups(), error handling, json parsing, etc.)
Viewcontroller.swift
apiObj.sessionSearch(domain) { result in
Log.info!.message("result: \(result)")
})
ApiObj.swift
func sessionSearch(domain: String, sessionCompletion: (result: SearchResult) -> ()) {
// Make request to /search/ url
let task = session.dataTaskWithRequest(request) { data, response, error in
let searchCookie = parseCookieFromResponse(data!)
********* pseudo code **************
var progress: Int = 0
var results = SearchResults()
while (progress != 100) {
// Make requests to /results/ until search is complete
self.getResults(searchCookie) { searchResults in
progress = searchResults.search_pct_complete
if (searchResults == 100) {
completion(searchResults)
} else {
sleep(5 seconds)
} //if
} //self.getResults()
} //while
********* pseudo code ************
} //session.dataTaskWithRequest(
task.resume()
}
func getResults(cookie: String, completion: (searchResults: NSDictionary) -> ())
let request = buildRequest((domain), url: NSURL(string: ResultsUrl)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
let theResults = getJSONFromData(data!)
completion(theResults)
}
task.resume()
}
Well first off, it seems weird that there is no API with a GET request which simply returns the result - even if this may take minutes. But, as you mentioned, you cannot change the API.
So, according to your description, we need to issue a request which effectively "polls" the server. We do this until we retrieved a Search object which is completed.
So, a viable approach would purposely define the following functions and classes:
A protocol for the "Search" object returned from the server:
public protocol SearchType {
var searchID: String { get }
var isCompleted: Bool { get }
var progress: Double { get }
var result: AnyObject? { get }
}
A concrete struct or class is used on the client side.
An asynchronous function which issues a request to the server in order to create the search object (your #1 POST request):
func createSearch(completion: (SearchType?, ErrorType?) -> () )
Then another asynchronous function which fetches a "Search" object and potentially the result if it is complete:
func fetchSearch(searchID: String, completion: (SearchType?, ErrorType?) -> () )
Now, an asynchronous function which fetches the result for a certain "searchID" (your "search_cookie") - and internally implements the polling:
func fetchResult(searchID: String, completion: (AnyObject?, ErrorType?) -> () )
The implementation of fetchResult may now look as follows:
func fetchResult(searchID: String,
completion: (AnyObject?, ErrorType?) -> () ) {
func poll() {
fetchSearch(searchID) { (search, error) in
if let search = search {
if search.isCompleted {
completion(search.result!, nil)
} else {
delay(1.0, f: poll)
}
} else {
completion(nil, error)
}
}
}
poll()
}
This approach uses a local function poll for implementing the polling feature. poll calls fetchSearch and when it finishes it checks whether the search is complete. If not it delays for certain amount of duration and then calls poll again. This looks like a recursive call, but actually it isn't since poll already finished when it is called again. A local function seems appropriate for this kind of approach.
The function delay simply waits for the specified amount of seconds and then calls the provided closure. delay can be easily implemented in terms of dispatch_after or a with a cancelable dispatch timer (we need later implement cancellation).
I'm not showing how to implement createSearch and fetchSearch. These may be easily implemented using a third party network library or can be easily implemented based on NSURLSession.
Conclusion:
What might become a bit cumbersome, is to implement error handling and cancellation, and also dealing with all the completion handlers. In order to solve this problem in a concise and elegant manner I would suggest to utilise a helper library which implements "Promises" or "Futures" - or try to solve it with Rx.
For example a viable implementation utilising "Scala-like" futures:
func fetchResult(searchID: String) -> Future<AnyObject> {
let promise = Promise<AnyObject>()
func poll() {
fetchSearch(searchID).map { search in
if search.isCompleted {
promise.fulfill(search.result!)
} else {
delay(1.0, f: poll)
}
}
}
poll()
return promise.future!
}
You would start to obtain a result as shown below:
createSearch().flatMap { search in
fetchResult(search.searchID).map { result in
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
This above contains complete error handling. It does not yet contain cancellation. Your really need to implement a way to cancel the request, otherwise the polling may not be stopped.
A solution implementing cancellation utilising a "CancellationToken" may look as follows:
func fetchResult(searchID: String,
cancellationToken ct: CancellationToken) -> Future<AnyObject> {
let promise = Promise<AnyObject>()
func poll() {
fetchSearch(searchID, cancellationToken: ct).map { search in
if search.isCompleted {
promise.fulfill(search.result!)
} else {
delay(1.0, cancellationToken: ct) { ct in
if ct.isCancelled {
promise.reject(CancellationError.Cancelled)
} else {
poll()
}
}
}
}
}
poll()
return promise.future!
}
And it may be called:
let cr = CancellationRequest()
let ct = cr.token
createSearch(cancellationToken: ct).flatMap { search in
fetchResult(search.searchID, cancellationToken: ct).map { result in
// if we reach here, we got a result
print(result)
}
}.onFailure { error in
print("Error: \(error)")
}
Later you can cancel the request as shown below:
cr.cancel()