I'm trying to make a simple get request to Google Places API from my vapor web service.
This is what my controller looks like:
import Vapor
import HTTP
import VaporPostgreSQL
final class MainController {
var currentDroplet: Droplet!
func addRoutes(drop: Droplet) {
currentDroplet = drop
drop.get("places",String.self, String.self, handler: getNearbyPlaces)
}
func getNearbyPlaces(request: Request, lat: String, long: String) throws -> ResponseRepresentable {
let googleAPIKey = "MY_KEY"
let googlePlacesBaseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch"
let url = googlePlacesBaseURL + "/json?location=\(lat),\(long)&radius=500&types=food&key=" + googleAPIKey
print(url)
let apiResponse = try drop.client.get(url)
print(apiResponse)
return apiResponse.json != nil ? apiResponse.json! : "Something went bad"
}
}
It should be as simple as that, however when I call it, the request keeps hanging for a long time and then it returns 500.
Note that the printed url in the console does work fine directly in the browser.
I couldn't figure out a useful way to catch and debug any errors too.
I needed to add import Foundation and drop.client = FoundationClient.self to main.swift to get a similar call to work.
Related
Now I'm developing cloud functions.
Please teach me how to get data from call cloud functions using swift app.
I use TypeScript in cloud functions as backend service.
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp(functions.config().firebase)
export const helloWorld = functions
.https.onCall((data: any, context: any) => {
functions.logger.info("Hello logs!", { structuredData: true })
return { result: "Hello World" }
})
And in frontend I use swift.
func callCloudfunction(){
functions.httpsCallable("helloWorld").call(["name": "taro"]) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(message)
}
}
if let data = (result?.data as? [String: Any]), let text = data["result"] as? String {
print("SUCCESS: \(text)")
}
}
}
In Swift I think functions.httpsCallable("helloWorld").call(["name": "taro"]) method is like http post request. So in TypeScript I can get the name's data using data argument.
But I don't know how to get result data from TypeScript. In TypeScript I created json { result: "Hello World" }.
How do I fix the process in swift? I think this code below is not good.
if let data = (result?.data as? [String: Any]), let text = data["result"] as? String {
print("SUCCESS: \(text)")
}
And please tell me how to handle error process.
Some class and property in error handling are already deprecated .FunctionsErrorDomain and FunctionsErrorDetailsKey.
Please teach me how to handle getting data and handle error process.
Thank you.
I want to create tests for user account creation, so I can confirm my app is uploading the correct data, but I'm struggling to find the right way to do it. So far I've been unsuccessful in writing a test that works. This error prevents me from getting any further:
caught error: "An error occurred when accessing the keychain. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo dictionary will contain more information about the error encountered"
I've not written tests in Xcode before, and I've had lots of trouble finding guides on this sort of issue. Here is my test code:
import XCTest
import Firebase
import FirebaseFirestoreSwift
import SwiftUI
#testable import Baseline
class AccountTests: XCTestCase {
var baselineApp: BaselineApp!
override func setUpWithError() throws {
baselineApp = BaselineApp()
}
override func tearDownWithError() throws {
async {
let idString = baselineApp.authenticationState.loggedInUser?.uid
if let idString = idString {
try await baselineApp.authenticationState.deleteUser(id: idString)
}
baselineApp.authenticationState.signout()
}
}
func testAccountCreation() async throws {
let firstName = "Test"
let lastName = "Xcode"
let email = "testdummy5684+XCtest#gmail.com"
let password = "testingForXcode1234"
let gender = UserDocument.Gender.male
let birthdate = Timestamp(date: Date())
let suggestedRank = 0
let image = UIImage(named: "social")?.jpegData(compressionQuality: 1.0)
try await baselineApp.authenticationState.signupNewUser(email: email, password: password, firstName: firstName, lastName: lastName, gender: gender, jpegImageData: image, suggestedRank: suggestedRank, birthdate: birthdate)
let foundUserResult = baselineApp.authenticationState.loggedInUser
let foundUserDocumentResult = baselineApp.authenticationState.loggedInUserDocument
let foundUser = try XCTUnwrap(foundUserResult)
let foundUserDocument = try XCTUnwrap(foundUserDocumentResult)
XCTAssertNotNil(foundUserDocument, "Failed to get user document")
XCTAssertEqual(firstName, foundUserDocument.firstName, "Tried \(firstName), but got \(foundUserDocument.firstName)")
}
}
I'm creating an instance of my app where my #main is, because I have observable objects instantiated there, alongside Firebase's config settings. I have keychain sharing enabled, and my app works otherwise (like logging into a pre-existing account).
I'm running Xcode 13.0 beta (13A5155e) and targeting iOS 15.
Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.
I'm coming from using tooling such as SuperTest with NodeJS and looking for relevant equivalents to support testing with Vapor 3 and server side swift.
I see a pattern of using making a testable application with Vapor 3 to do testing of endpoints, examples being https://github.com/raywenderlich/vapor-til/blob/master/Tests/AppTests/Application%2BTestable.swift and the write-up at https://medium.com/swift2go/vapor-3-series-iii-testing-b192be079c9e.
When using these in tests, the format generally looks something like:
func testGettingASingleUserFromTheAPI() throws {
let user = try User.create(name: usersName, username: usersUsername, on: conn)
let receivedUser = try app.getResponse(to: "\(usersURI)\(user.id!)", decodeTo: User.Public.self)
XCTAssertEqual(receivedUser.name, usersName)
XCTAssertEqual(receivedUser.username, usersUsername)
XCTAssertEqual(receivedUser.id, user.id)
}
(from Vapor-TIL example code)
In all of these examples, the return values are really set to be handed back to something decodable (the decodeTo: kind of setup). In some cases in my Vapor 3 code, I want to just validate some non-JSON encoded results - just simple strings, and validate the results - but I've not found the methods to get into the content or convenient ways to validate it with XCTAssert.
response.content is available, a container around the overall response (of type ContentContainer). Are there some examples or good ways at getting to the underlying content representation to validate them directly?
You could write your own additional methods in Application+Testable like
func getRawResponse(to path: String) throws -> Response {
return try self.sendRequest(to: path, method: .GET)
}
func getStringResponse(to path: String) throws -> String {
let response = try self.getRawResponse(to: path)
guard let data = response.http.body.data,
let string = String(data: data, encoding: .utf8) else {
throw SomeError("Unable to decode response data into String")
}
return string
}
and then call them to get either raw Response or decoded String like
func testGettingHelloWorldStringFromTheAPI() throws {
let string = try app. getStringResponse(to: "some/endpoint")
XCTAssertEqual(string, "Hello world")
}
I just began learning Swift, I am working with an API behind, the problem is that : Swift don't wait my function finish therefore my last function appears like no code was done before.
import Foundation
import UIKit
import Alamofire
import SwiftyJSON
// Classes
class User {
init(data: Any) {
self.sex = JSON(data)["sex"].string!
print(self.sex)
}
var id: Int = 1
var online: Bool = false
var picture: String = ""
var sex: String = "Male"
}
// Fonctions
func getBackground(_ apiURL: String, completion : #escaping(_ :Any) -> Void) {
// Requête API avec Alamofire + SwiftyJSON
AF.request(apiURL, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let jsonData = JSON(value)
completion(jsonData["results"])
case .failure(let error):
print(error)
}
}
}
// Requête de connexion
getBackground("https://x..me", completion: { response in
let user = User(data: response)
})
print(String(user.id) + ": " + String(user.online!))
Screenshot here
I have this error: "Use of unresolved identifier 'user'", I guess that Swift don't get the fact that User was defined previously
All my work works perfectly with my API, the "self.sex" is set and showed when I build the code.
But I'm still "locked" like I can't do nothing after this code because Swift don't want to wait.
I tried the function async and sync but then, all my next code has to be under a function...
Thanks in advance
There's no need to wait.
Put the print line – and the code which proceeds the user – in the completion closure
getBackground("https://a.clet.me", completion: { response in
let user = User(data: response)
print(String(user.id) + ": " + String(user.online!))
})
The error Use of unresolved identifier 'user' occurs because the local variable user is only visible inside the scope ({}) of its declaration.
So, this problem is all about variables life cycle.
// This happens first
getBackground("https://a.clet.me", completion: { response in
let user = User(data: response)
// This happens third, once the request has completed.
// user exists here.
// Only after this moment you should handle anything related to the user
})
// This happens second
// user DOESN'T exist here, swift will move on