I am trying to find a simple example of how inside a router a person would send a request to the vapor sample endpoint http://example.vapor.codes/json, receive a response and map it to a struct or class.
I've seen examples elsewhere for Vapor 2 but they are no longer relevant with Vapor 3 and the current Vapor 3 beta documentation isn't clear.
Something like...
router.get("sample") { req in
//1. create client
//2. send get request to sample endpoint at http://example.vapor.codes/json
//3. handle response and map to a struct or class
}
My goal is to go grab something off the endpoint, turn it into a struct or class and display it in a leaf view.
{"array":[0,1,2,3],"dict":{"lang":"Swift","name":"Vapor"},"number":123,"string":"test"}
Here is my outline for how I think it is done but I don't understand how to handle the response and process into the struct so that I can use it in my home.leaf in its html (I'm not concerned with the leaf part assume I have all the configuration for all that and imports already).
router.get("example"){ req -> Future<View> in
struct ExampleData: Codable {
var array : [Int]
var dict : [String : String]
}
return try req.make(Client.self).get("http://example.vapor.codes/json").flatMap(to: ExampleData.self) { res in
//not sure what to do to set the values of the ExampleData
}
return try req.view().render("home", ExampleData())
}
}
Example code
I strongly recommend you read the explaination below, but this is the code.
struct ExampleData: Codable {
var array : [Int]
var dict : [String : String]
}
// Register a GET /example route
router.get("example") { req -> Future<View> in
// Fetch an HTTP Client instance
let client = try req.make(Client.self)
// Send an HTTP Request to example.vapor.codes/json over plaintext HTTP
// Returns `Future<Response>`
let response = client.get("http://example.vapor.codes/json")
// Transforms the `Future<Response>` to `Future<ExampleData>`
let exampleData = response.flatMap(to: ExampleData.self) { response in
return response.content.decode(ExampleData.self)
}
// Renders the `ExampleData` into a `View`
return try req.view().render("home", exampleData)
}
Futures
A Future<Expectation> is a wrapper around the Expectation. The expectation can be successful or failed (with an Error).
The Future type can register callbacks which are executed on successful completion. One of these callbacks that we use here is flatMap. Let's dive into a regular map, first.
If you map a Future you transform the future's successful Expectation and transparently pass through error conditions.
let promise = Promise<String>()
let stringFuture = promise.future // Future<String>
let intFuture = stringFuture.map(to: Int.self) { string -> Int in
struct InvalidNumericString: Error {}
guard let int = Int(string) else { throw InvalidNumericString() }
return int // Int
}
intFuture.do { int in
print("integer: ", int)
}.catch { error in
print("error: \(error)")
}
If we complete the promise with a valid decimal integer formatted string like "4" it'll print integer: 4
promise.complete("4")
If we place any non-numeric characters in there like "abc" it'll throw an error inside the InvalidNumericString error which will be triggering the catch block.
promise.complete("abc")
No matter what you do, an error thrown from a map or flatMap function will cascade transparently through other transformations. Transforming a future will transform the Expectation only, and only be triggered on successful cases. Error cases will be copied from the "base future" to the newly transformed future.
If instead of completing the promise you fail the promise, the map block will never be triggered and the AnyError condition will be found in the catch block instead.
struct AnyError: Error {}
promise.fail(AnyError())
flatMap works very similarly to the above example. It's a map where the trailing closure returns a Future<Expectation> rather than Expectation.
So If we'd rewrite the map block to be a flatMap, although impractical, we'll end up with this:
let intFuture = stringFuture.flatMap(to: Int.self) { string -> Future<Int> in
struct InvalidNumericString: Error {}
guard let int = Int(string) else { throw InvalidNumericString() }
return Future(int) // Int
}
intFuture is still a Future<Int> because the recursive futures will be flattened from Future<Future<Int>> to just Future<Int>.
Content
The response.content.decode bit reads the Content-Type and looks for the default Decoder for this Content Type. The decoded struct will then be returned as a Future<DecodedStruct>, in this case this struct is ExampleData.
The reason the content is returned asynchronously is because the content may not have completely arrived in the HTTP response yet. This is a necessary abstraction because we may be receiving files upwards of 100MB which could crash (cloud) servers with a small amount of memory available.
Logic
Back to the original route:
First make a client
Make a request to http://example.vapor.codes/json
Read the content from the Future<Response> asynchronously
Render the results into the view asynchronously
Return the Future<View>
The framework will understand that you're returning a Future<View> and will continue processing other requests rather than waiting on the results.
Once the JSON is received, this request will be picked up again and processed into a response which your web browser will receive.
Leaf is built on top of TemplateKit which will await the future asynchronously. Just like Vapor, Leaf and TemplateKit will understand Futures well enough that you can pass a Future instead of a struct (or vice versa) and they'll switch to anothe request until the future is completed, if necessary.
Related
I am new to Swift and SwiftUI and this has been driving me nuts.
I have a list of items coming from a database, each associated with a User ID.
I need to display each item together with some user data (which also comes from the database). I want to save on database calls for getting user data for those users who have already appeared in my list of items.
In order to do that, I create a dictionary of user data, which I populate with each new user. If a user id is already in the dictionary, I won't be querying the database and will instead be using the cached user data in the dictionary.
So that was the idea and it seemed pretty straightforward. So I wrote the following
Main loop in the View goes through the list of items:
ForEach(xlist, id: \.self) { xentry in
Text(myModel.dictUser[xentry.actor_id]?.FirstName ?? "")
}.onAppear(perform: {myModel.cacheUser(uid: xentry.actor_id)})
in myModel, I try to cache the user:
#MainActor class myModel: ObservableObject {
#Published var dictUser: [String: STuser] = [:]
func cacheUser(uid: String) {
Task.init {
do {
if nil == dictUser[uid] {
dictUser[uid] = try await GetUserInfoFromDB(uid:uid)
}
} catch {
}
}
}
}
This should work but doesn't: the nil == dictUser[uid] in some cases (normally after a couple of entries) fails to correctly evaluate. I can see in the debugger that dictUser[uid] is clearly not a nil and contains valid data, yet the execution continues onto GetUserInfoFromDB.
Not sure what I'm doing wrong here. Any help appreciated.
You are trying to load data from a relatively slow, asynchronous source (a database in this case) and you want to utilise a cache to avoid the overhead of repeated database calls for the same item. A sensible approach.
With the cached items, there are three possible states:
it's already been requested and is in the cache
it's not been requested yet
it's been requested but the request is still in progress (and so the data item isn't in in the cache)
The first case is easy: if it's in the cache return it. The second case is also seemingly straightforward - you need to request the item from the database.
The last scenario is more complicated - how do you record that a request has already been made but not yet returned, and then wait for it to return it's data and send it to all those items that have requested it.
The obvious construct to represent something that can be in multiple states is an enum. For example:
enum cacheEntry {
case complete
case inProgress
}
but it's not that simple: you also want to associate each state with the data it returns from the cache.
The complete case is easy - use an associated value of the data item, in your case STUser.
The inProgress case is more complex as you want it to record the asynchronous activity that is in progress and when that activity completes return it's data item. The way to handle this is to store the asynchronous task as the associated value. So your cache entry looks like this:
enum CacheEntry {
case complete(STUser)
case inProgress(Task<STUser, Error>)
}
var cache: [String: CacheEntry] = [:]
(Note: if you're not handling the error you can replace the Error in the generic with Never.)
The question then becomes how do you use this construct?
Create a method to query the cache that can work with the async nature of the operation:
func entry(for bid: String) async throws -> STUser {
if let cacheEntry = imageCache[uid] {
switch cacheEntry {
case let .inProgress(task):
return try await task.value. //wait for the task to complete then return it's completion value
case let .downloaded(stUser):
return stUser //the item is already in the cache so return it
}
}
//There is no entry in the cache for the bid at this point
// Therefore create a task to retrieve the data asynchronously
let task = Task {
try await GetUserInfoFromDB(uid:uid)
}
//and store the task in the cache against the `uid` ready for any subsequent requests
imageCache[url] = .inProgress(task)
//process the task for the initial request
do {
//wait for the task to complete and then access its returned value
let item = try await task.value
cache[uid] = .downloaded(item) //replacing the 'inProgress' entry in the cache with the
return item // and return the retrieved value
} catch {
//if error, delete entry from the cache and handle the error
imageCache[url] = nil
throw error
}
}
The final complication now is that you have a synchronous dictionary that is being updated by an asynchronous task, with the potential for data races. To overcome this wrap the whole cache type in an actor to ensure the access to the cache is coordinated.
actor Cache {
enum CacheEntry {...}
func entry(for bid: String) async throws -> STUser {...}
}
I have a class that contains some Alamofire code to get JSON from a server, convert it into a pre-defined model and then return that model as an array.
Here is the code
func GetLights(completionHandler: #escaping (DataResponse<[LightList]>) -> Void) -> Alamofire.DataRequest {
return AF.request(APIString + "/lights").responseJSON { response in
let LightListResponse = response.flatMap { json in
try JSONDecoder().decode([LightList].self, from: response.data!)
}
completionHandler(LightListResponse)
}
}
func GetLightList() {
GetLights { response in
if let lights = response.value {
print(lights)
}
}
}
I can breakpoint through to the JSONDecoder and see the json via debug but the print line at the end prints nothing, it doesn't even hit a breakpoint.
Can anyone see what I'm doing wrong? I think I'm using the completion handler correctly?
I am calling the GetLightList via a SwiftUI file like so:
func InitList() {
let requests = Requests()
requests.GetLightList()
}
You shouldn't be doing this using responseJSON, as that method has already parsed the JSON using JSONSerialization and made it available to you as part of the response. Instead, you should use responseDecodable, since you already have a Decodable type.
return AF.request(apiString + "/lights").responseDecodable(of: [LightList].self) { response in
completionHandler(response)
}
However, it's often best not to expose the DataResponse type produced by Alamofire but instead use the Result from the response in your completion handler.
Additionally, updating your styling to match Swift's recommended style will help you write consistent code. Namely, methods and variable names should start with a lowercase letter to separate them from type declarations. You can see this in your code samples where it thinks things like "APIString" are types and not variables.
Finally, it's often helpful to not overload get as a method prefix. For network calls I like using fetch when requesting a resource. e.g. fetchLights.
I'm working on an app which needs to query multiple APIs. I've come up with classes for each API provider (and in more extreme cases, a class for each specific API Endpoint). This is because each API query is expected to return a very strict type of response, so if an API can, for instance, return both user profiles and profile pictures, I only want a response to be specific to either of those.
I've implemented it roughly in the following manner:
protocol MicroserviceProvider {
associatedtype Response
}
protocol ProfilePictureMicroserviceProvider: MicroserviceProvider {
func getPicture(by email: String, _ completion: (Response) -> Void)
}
class SomeProfilePictureAPI: ProfilePictureMicroserviceProvider {
struct Response {
let error: Error?
let picture: UIImage?
}
func getPicture(by email: String, _ completion: (Response) -> Void) {
// some HTTP magic
// will eventually call completion(_:) with a Response object
// which either holds an error or a UIImage.
}
}
Because I want to be able to Unit Test classes that will rely on this API, I need to be able to inject that profile picture dependency dynamically. By default it will use SomeProfilePictureAPI but when running tests I will be able to replace that with a MockProfilePictureAPI which will still adhere to ProfilePictureMicroserviceProvider.
And because I'm using associated types, I need to make classes that depend on ProfilePictureMicroserviceProvider generic.
At first, I naively did try to write my view controller like such
class SomeClass {
var profilePicProvider: ProfilePictureMicroserviceProvider
}
But that just led the frustratingly famous 'Protocol ProfilePictureMicroserviceProvider can only be used as a generic constraint because it has Self or associated type requirements' compile-time error.
Now I've been reading up on the issue over the last couple days, trying to wrap my head around Protocols with Associated Types (PATS), and figured I'd take the route of generic classes like such:
class SomeClass<T: ProfilePictureMicroserviceProvider> {
var profilePicProfider: T = SomeProfilePictureAPI()
}
But even then I get the following error:
Cannot convert value of type 'SomeProfilePictureAPI' to specified type 'T'
Even though having T being constrained to the ProfilePictureMicroserviceProvider protocol, and having SomeProfilePictureAPI adhere to it...
Basically the main idea was to reach 2 objectives: enforce Microservice structure with mandatory Response type, and make each Microservice mock-able for unit tests of dependent classes.
I'm now stuck with choosing either one of the two as I can't seem to make it work. Any help telling me what I'm doing wrong would be most welcome.
I've also had a look at type-erasure. But this to me seems very whacky and quite an effort for something that looks wrong on many aspects.
So basically my question is two-fold: how can I enforce my Microservices to define their own Response type ? And how can I easily replace them by mock microservices in classes that depend on them ?
You have to turn these requirements around;
Instead of injecting a MicroServiceProvider into each request, you should write a generic MicroService 'Connector' Protocol that should define what it expects from each request, and what each request expects it to return.
You can then write a TestConnector which conforms to this protocol, so that you have complete control over how your requests are handled. The best part is, your requests won't even need to be modified.
Consider the following example:
protocol Request {
// What type data you expect to decode and return
associatedtype Response
// Turn all the data defined by your concrete type
// into a URLRequest that we can natively send out.
func makeURLRequest() -> URLRequest
// Once the URLRequest returns, decode its content
// if it succeeds, you have your actual response object
func decode(incomingData: Data?) -> Response?
}
protocol Connector {
// Take in any type conforming to Request,
// do whatever is needed to get back some potential data,
// and eventually call the handler with the expected response
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void)
}
These are essentially the bare minimum requirements to setup such a framework. In real life, you'll want more requirements from your Request protocol (such as ways to define the URL, request headers, request body, etc).
The best part is, you can write default implementations for your protocols. That removes a lot of boilerplate code! So for an actual Connector, you could do this:
extension Connector {
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void) {
// Use a native URLSession
let session = URLSession()
// Get our URLRequest
let urlRequest = request.makeURLRequest()
// define how our URLRequest is handled
let task = session.dataTask(with: urlRequest) { data, response, error in
// Try to decode our expected response object from the request's data
let responseObject = request.decode(incomingData: data)
// send back our potential object to the caller's completion block
handler(responseObject)
}
task.resume()
}
}
Now, with that, all you need to do is implement your ProfilePictureRequest like this (with extra example class variables):
struct ProfilePictureRequest: Request {
private let userID: String
private let useAuthentication: Bool
/// MARK: Conform to Request
typealias Response = UIImage
func makeURLRequest() -> URLRequest {
// get the url from somewhere
let url = YourEndpointProvider.profilePictureURL(byUserID: userID)
// use that URL to instantiate a native URLRequest
var urlRequest = URLRequest(url: url)
// example use: Set the http method
urlRequest.httpMethod = "GET"
// example use: Modify headers
if useAuthentication {
urlRequest.setValue(someAuthenticationToken.rawValue, forHTTPHeaderField: "Authorization")
}
// Once the configuration is done, return the urlRequest
return urlRequest
}
func decode(incomingData: Data?) -> Response? {
// make sure we actually have some data
guard let data = incomingData else { return nil }
// use UIImage's native data initializer.
return UIImage(data: data)
}
}
If you then want to send a profile picture request out, all you then need to do is (you'll need a concrete type that conforms to Connector, but since the Connector protocol has default implementations, that concrete type is mostly empty in this example: struct GenericConnector: Connector {}):
// Create an instance of your request with the arguments you desire
let request = ProfilePictureRequest(userID: "JohnDoe", useAuthentication: false)
// perform your request with the desired Connector
GenericConnector().perform(request) { image in
guard let image = image else { return }
// You have your image, you can now use that instance whichever way you'd like
ProfilePictureViewController.current.update(with: image)
}
And finally, to set up your TestConnector, all you need to do is:
struct TestConnector: Connector {
// define a convenience action for your tests
enum Behavior {
// The network call always fails
case alwaysFail
// The network call always succeeds with the given response
case alwaysSucceed(Any)
}
// configure this before each request you want to test
static var behavior: Behavior
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void) {
// since this is a test, you don't need to actually perform any network calls.
// just check what should be done
switch Self.behavior {
case alwaysFail:
handler(nil)
case alwaysSucceed(let response):
handler(response as! T)
}
}
}
With this, you can easily define Requests, how they should configure their URL actions and how they decode their own Response type, and you can easily write mocks for you connectors.
Of course, keep in mind that the examples given in this answer are quite limited in how they can be used. I would highly suggest you to take a look at this library I wrote. It extends this example in a much more structured way.
Im unit testing my Alamofire code at the moment. Im trying to create a reusable method that I can pass in a status code and a method that will need to be invoked as parameters. I was thinking of using a closing block but the syntax is escaping me and it doesnt help that I have started with Swift. Here is an example:
func parseJsonResponse(response: Response <AnyObject, NSErrror>){
//parsing
}
func test_networkRequest(withStatusCode statusCode: Int, /* parse function as parameter */) {
stub(isHost("https://httpbin.org")) { _ in
return OHHTTPStubsResponse(fileAtPath:OHPathForFile("wsresponse.json", self.dynamicType)!,
statusCode:statusCode, headers:["Content-Type":"application/json"])
})
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.responseJSON { response in
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
XCTAsertNotNil(response)
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
//method should go here
self.parseJsonResponse(response)
}
}
I want to be able to make 'test_networkRequest' reusable in all my classes, and there will be different types of json to be parsed and handled in various ways. This is why I would like to be able to pass in a function as a parameter into 'test_networkRequest'.
I hope my end goal is clear, and I am open to suggestions if I am off track. :)
To create a block, do something like this:
class Foo {
var block: (statusCode: Int) -> Void
var optionalBlock: ((argA: Int, argB: String) -> Bool)? // in case you're curious
init() {
block = { (statusCode: Int) -> Void in
// This is the block that can now be passed
}
}
}
You may want to think about your decision to use a block though. From the sentence "I want to be able to make 'test_networkRequest' reusable in all my classes..." it sounds like this may be better as a function in a superclass.
I have an HTTPService which returns an Observable<NSData>. My goal is to compose that service into another service, ServiceA which transforms that data for my use case. Using Observable.create in RxSwift 2.0.0-rc.0 in ServiceA it's straight forward enough. My question is how to properly handle the disposable returned from the subscription of the HTTPService.
If I don't do anything I get the compile time warning that the result of call is unused: http://git.io/rxs.ud. I understand from reading that if I do nothing it's likely ok: (where xs mentioned below is let xs: Observable<E> ....
In case xs terminates in a predictable way with Completed or Error message, not handling subscription Disposable won't leak any resources, but it's still preferred way because in that way element computation is terminated at predictable moment.
So here is how I am currently addressing it, and also where I am wondering if I am doing this properly or if I have misunderstood something.
public struct ServiceA{
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
let disposable = request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
// Is this the right way to deal with the
// disposable from the subscription in this situation?
return AnonymousDisposable{
disposable.dispose()
}
}
}
}
As documentation says
subscribe function returns a subscription Disposable that can be used to cancel computation and free resources.
Preferred way of terminating these fluent calls is by using
.addDisposableTo(disposeBag) or in some equivalent way.
When disposeBag gets deallocated, subscription will be automatically
disposed.
Actually your example looks fine in terms of rules, but it loos pretty bad ;) (Also it would be ok, if you would just return this disposable) :
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
return request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
}
But as you you returning Observeble I wonder, why you dont just use map operator ?
In your example it would be something like this:
public static func changes() -> Observable<ChangeSet> {
return HTTPService.get("https://httpbin.org/get")
.map(ChangeSet.init)
}