How to handle PUT HTTP request in Vapor? - swift

The only way I've found to update a record in Vapor is this:
drop.get("update") { request in
guard var first = try Acronym.query().first(),
let long = request.data["long"]?.string else {
throw Abort.badRequest
}
first.long = long
try first.save()
return first
}
However it's not a very RESTful way of doing it since it's performing a GET request with a parameter instead of a PUT request.
How does one perform a PUT request in Vapor?

As it turns out, performing PUT, as well as other HTTP methods are as simple as changing .get() or .post() to .put() or any other HTTP methods.
As for my question, to create a PUT function in Vapor, just simply add a .put method that takes an Int (Or String, or any data type you'd like), and accept a JSON (Or whatever format you'd like), and simply update like it's a POST request.

drop.put("update") { request in
guard var first = try Acronym.query().first(),
let long = request.data["long"]?.string else {
throw Abort.badRequest
}
first.long = long
try first.save()
return first
}

Related

Kentico-cloud Swift SDK ContentType does not have usable properties

I am using the Kentico-cloud Swift SDK to grab a bunch of elements from the CMS using the Delivery API in the background.
One of the Swift SDK methods allows me to get a ContentType for a certain element on the CMS so I can then map it to an object in my code. Here's the code:
self.client.getContentType(name: codename, completionHandler: { (isSuccess, contentType, error) in
guard error == nil else {
print(error!)
return
}
if isSuccess {
if let type = contentType {
print(type)
self.client.getItem(modelType: type, itemName: codename, completionHandler: { (isSuccess, deliveryItem, error) in
if isSuccess {
// save this Element
print(deliveryItem)
} else {
if let error = error {
print(error)
}
}
})
}
}
})
the attribute codename is the name of the object I am trying to find the ContentType for. The call succeeds and I get my ContentType object, unfortunately, it does not have any properties in it that aren't nil.
I assume it should give me the name of the type as a String so I can then map it to my class.
Could you verify you have valid content type codename in the name parameter? I've tried to reproduce it (see attached screenshot) and everything works on my side (there is also test for this feature which passes as well in GetContentType.swift).
Could you post the value of a requestUrl property from DeliveryClient.swift getContentType() method line 176?
Edit: Oh, from your screen on the GitHub issue I can see you are trying to get the content type with the codename of the item which in wrong. You should use the codename of the content type.
From the docs for getContentType() method:
/**
Gets single content type from Delivery service.
- Parameter name: The codename of a specific content type.
- Parameter completionHandler: A handler which is called after completetion.
- Parameter isSuccess: Result of the action.
- Parameter contentTypes: Received content type response.
- Parameter error: Potential error.
*/
You can learn more about content types here.
I also had the same thought usps tracking but thanks that you have provided a question.
Thanks and Regards,
Shane.

RxSwift - Repeat observable until predicate

I'm fairly new to RxSwift and have been banging my head against the following problem for two days now.
I wrapped a closure that reads a partial JSON formatted string from an API:
func readResult() -> Observable<String> {
return Observable<String>.create { observable -> Disposable in
API.readValue() { (result: Result<String>) in
switch result {
case .success(let value): observable.onNext(value)
case .failure(let error): observable.onError(error)
}
observable.onCompleted()
}
return Disposables.create()
}
}
As the result from readValue only contains a chunk of the JSON formatted string, and I need to recursively call this method to get the full string. Therefore, it is important to start a new reading only when the previous one has finished.
I tried using an Observable.timer and scan to accumulate the results until I can successfully decode the json, but using a timer does not guarantee that the previous reading finished.
I also thought about using concat but as I don't know the length of the full JSON string in advance, I cannot write something like this:
Observable.concat(readResult(), readResult())
How could I ensure that the readResult function gets called until I can successfully decode the resulting JSON string?
In principle, .reduce() should be the right tool for the job.
Not sure why are you building Observable from scratch the hard way instead of using .from() factory method.
I would probably do it as follows (pseudocode):
let subject = PublishSubject<Observable<String>>.create()
let result = subject.switchLatest().reduce { /* update result */ }
subject.onNext(
Observable.from( /* service call */ ).subscribeOn( /* some serial scheduler */ )
) // repeat as needed
UPDATE
See the more specific solution in comments.

Swift 3.0 down casting and best practices

My question here is mostly in trying to be a better programmer, not that my code doesn't work.
Is downcasting something considered good enough (as in 'best practices') when writing your code? I'll give an example below with URLSession. I understand Swift gives you the tools to do it with as! or as?, but something tells me we shouldn't be doing it (or there are better ways to do it). I just can't see them. For example, consider this code that retrieves a web page from a HTTP GET request:
guard let url = URL(string: apiEndpoint) else {
return
}
if let scheme = url.scheme {
if scheme != "http" || scheme != "https" {
return
}
}
// More code
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
// Error handling here
}
else {
guard let httpResponse = response as? HTTPURLResponse else {
return
}
// Code here
}
}.resume()
So, the above code works, but my question is on the guard let httpResponse = response as? HTTPURLResponse statement. dataTask is supposed to return an URLResponse object back to the delegate, in this case, my variable response. However, this class obviously doesn't have a status code. So if I do the following, Swift will give me a compile error:
guard let httpResponse = response else {
return nil
}
statusCode = response.statusCode
Because response is supposed to be an URLResponse object, so it fails Xcode's check.
In order to obtain that information I need to either force down casting to HTTPURLResponse with as! (I've checked that the request is http/https earlier) or check with an optional as I did above.
Now, I don't want to use big wrappers like Alamofire because my API is very very simple. I want to write my own wrapper around URLResponse and return an object that other parts of my code can use. I don't know how the AF guys solve this problem. What would be the Swift way of dealing with this? Is down casting fine as a "best practice"? Is there a better way of getting the HTTPURLResponse object?
First of all, strictly spokenĀ a block based API doesn't have a delegate.
URLSessionDataTask is a quite versatile class and can be used for other purposes than an HTTP request hence the response object is the more generic URLResponse class and it is optional.
In case of an HTTP request the API returns the more specific subclass HTTPURLResponse so the object must be casted down to get access to the specific properties of HTTPURLResponse like statusCode. So yes, down casting is fine as a "best practice".
Here are two suggestions to be a better programmer
Variable names are supposed to start with a lowercase letter e.g. urlString
As already mentioned in a comment use more descriptive variable names than a single character.
There is another serious error in the code: The completion handler has no return value so you will get a compiler error Unexpected non-void return value in void function.
Side-note: The check for the scheme and for nil is not needed since the literal string https://www.google.com clearly contains the scheme and is a valid URL.

What is the way to get received data out of URLSession?

Recently, I attempted to write my own Telegram Bot API. However, the project has seem to have hit a brick wall with URLSession (formerly NSURLSession) issues.
The call structure is as follows:
getMe() -> getData() -> NSURLSession
Ideally, I would like to have the data returned from NSURLSession passed back to getMe() for the application to process. However, this has not proven possible with the methods I have tried.
Below is the code I have been using. synthesiseURL() generates the URL that the app should open the session to in order to perform the action on the Telegram Bot API. A template of the URL generated by synthesiseURL() is https://api.telegram.org/bot\(token)/\(tgMethod).
// NSURLSession getData: gets data from Telegram Bot API
func getData(tgMethod: String, arguments: [String] = [String](), caller: String = #function) {
let url = synthesiseURL(tgMethod: "getMe"), request = NSMutableURLRequest(url: url)
var receivedData = String()
let session = URLSession.shared.dataTask(with: request as URLRequest) { data, response, err in
if err != nil {print(err!.localizedDescription); return}
DispatchQueue.main.async {
receivedData = String(data: data!, encoding: String.Encoding.nonLossyASCII)!
print(receivedData)
}
}
session.resume()
}
I have been trying to get getData to pass receivedData, which contains the Bot API's response, back to the function getMe.
func getMe() -> String {
HTTPInterface(botToken: token).get(tgMethod: "getMe")
return [???] // here's where the data from getData() should come
}
I have tried completion handlers, callbacks, asynchronous calls to the main thread etc, but none seem to be working as expected (getMe() returns an empty string).
Why is this so, and can it be fixed?
The fundamental issue is that your getMe() function is declared as having an immediate String return type, but it depends on a delayed / asynchronous call to get that string. The timeline looks something like this:
getMe() is called by some client code
getMe() kicks of the method that launches a URLSession to get the data
getMe() moves to the next line of execution and returns a string (still empty at this point). The getMe() function has now returned and the client code execution has continued forward with the empty String result
The URLSession completes with data, but execution has already moved on so the data doesn't get used anywhere
The easiest fix is to make your getMe function not have a return type, but to also call back to a closure parameter when the URLSession data comes back, something like:
func getMe(callback:String->()) {
//getData and pass a closure that executes the callback closure with the String data that comes back
}
The less easy fix is to use a technique like dispatch semaphores to prevent getMe() from returning a result until the URLSession data comes back. But this sort of approach is likely to stall your main thread and is unlikely to be the right choice.

Call method ViewDidload Swift

i currently developpe an app which request data from a webservice, and one the datas are retrieve, i call a method to create some "swype" card like tinder.
The problem is, i do not success to call my method in the viewDidload, this is my code :
if(success == 1) {
NSLog("Recherche OK");
//Call the swype method
}
This is my method :
func fetchData(int: Int, completion: (()->())?) {
let newCard = Model()
//ttp://thecatapi.com/api/images/get?format=src&type=png
if let url = NSURL(string: "http://test.com/api/images/get?format=src&type=png") {
if let data = NSData(contentsOfURL: url) {
newCard.image = UIImage(data: data)
newCard.content = "test"
newCard.desc = "test"
self.data.append(newCard)
NSLog("fetch new data")
}
Have you got an idea ?
Please read more about getting NSData from a network call. Following is from a Apple Documentation
Do not use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the
dataTaskWithURL:completionHandler: method of the NSSession class. See
URL Loading System Programming Guide for details.
Also I noticed that the url you are showing redirects, so maybe that is the problem. Instead of trying to load it in a UIImage, you can create a UIWebview and show the image there.
You can find Swift related help regarding that in answer here