Cloud Function post request not sending error message properly to IOS app - swift

I have an iOS app receiving json data from a google cloud function.
The data is retrieved and works perfectly when there is no error.
However, when the cloud function returns an error, the function below recognizes error as nil
Client:
func initialize() {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
let clientSecret = json["clientSecret"] as? String,
let publishableKey = json["publishableKey"] as? String else {
///////////
//ERROR is not being recognized
/////////
let message = error?.localizedDescription ?? "Failed to decode response from server."
print("Error loading page: \(message)")
return
}
})
task.resume()
}
Client Output:
Failed to decode response from server.
Server:
firebase.functions().httpsCallable('myFunction')(message)
.then(result => {
// this works perfectly and is recognized by the swift function //
res.send({publishableKey: publishableKey, clientSecret: clientSecret});
})
.catch(err => {
// this is not recognized by the swift function //
console.log(err) // full of data
return res.status(400).send({error: err});
});
Logs(for error case):
Function execution took 240 ms, finished with status code: 400

If your requests fails I think that your error will come into the response parameter and you have to decode it. I think that the error parameter will be different than nil only if the server can't be reached or function does not exist. So basically in the else clause you will have to search into the response parameter for error.

Related

Simple post request in SwiftUI

I'm beginner in SwiftUI and I'm not familiar with variable management.
I'd like to send a very simple post request like this one with SwiftUI:
let full_path : String = "https://www.example.com/get_answer?my_message=current temperature"
I've tried with this piece of code but it didn't work.
if (URL(string: full_path) != nil) {
let url = URL(string: full_path)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
var decodedAnswer = String("")
do {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print(response)
decodedAnswer = String(decoding: response, as: UTF8.self)
}
}
I have the following error:
Value of optional type 'URLResponse?' must be unwrapped to a value of
type 'URLResponse'
I don't know how to get the response.
How can I get the response from a simple Post request in SwiftUI?
Multiple issues here.
You are trying to decode the URLResponse object, but what you want is the data object in the decoder.
You seem to not know about optionals. I would refer you to the basic Apple tutorials about this topic. You can find it with your favorite search engine.
You are in an async context here. Everything inside the url datasession closure will be execute after your network request returns. The code in your function will be completed by that moment and your var decodedAnswer will be out of scope. So move it out of the function in to the class/struct.
You probably want something like this:
This should be defined in class scope or you won´t be able to use it:
var decodedAnswer: String = ""
This should be in a function:
let full_path: String = "https://www.example.com/get_answer?my_message=current temperature"
if let url = URL(string: full_path) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
do {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
//This converts the optionals in to non optionals that could be used further on
//Be aware this will just return when something goes wrong
guard let data = data, let response = response, error == nil else{
print("Something went wrong: error: \(error?.localizedDescription ?? "unkown error")")
return
}
print(response)
decodedAnswer = String(decoding: data, as: UTF8.self)
}
task.resume()
}
}

Api call to .net C sharp web api project fails in swift but works fine

I am using a asp.net back end with a login end point but no matter what I DO in the swift version of this code I get a 415 when I use it in .net and sharp the api works am not sure what am doing wrong here.
And yes I have enabled transport protocol but its not decoding the jwt token correctly for me in swift
Basically the end point returns the jet token used for accessing the api in an object
let jwtAccessToken: String = ""
let urlString = "http://url.com/login" *** hidden for security
purposes but is correct ****
func CallWebApi()
{
// create the url with URL
let url = URL(string: urlString)! // change server url accordingly
let parameters: [String: Any] = [ "username":
"user1#domain.com", "password": "pass1"]
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded",
forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "Post"
do {
request.httpBody = try
JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
return
}
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
// check for fundamental networking error
print("error", error ?? URLError(.badServerResponse))
return
}
guard (200 ... 299) ~= response.statusCode else {
// check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
// do whatever you want with the `data`, e.g.:
do {
let responseObject = data
print(responseObject)
} catch {
print(error)
// parsing error
if let responseString = String(data: data, encoding: .utf8) {
print("responseString = \(responseString)")
} else {
print("unable to parse response as string")
}
}
}
task.resume()
}
MyModel is basically a string
import Foundation
class AuthenticationResponse: ObservableObject {
#Published var jwtToken: String
init(jwtToken: String) {
self.jwtToken = jwtToken
}
}
I think 20 years of c sharp in not helping and am doing things it way and not the swift way if someone could advice be great.
Also in csharp we were told its not great in keeping alive the http client as can degrade performance is this the same for swift and if any library's you can recommend makes the code a bit neater the api has swagger docs enabled.
Edit 3
Example response expected back
{
"id": "b181104e-ba3e-4dba-b124-4bb4a3873b17",
"firstName": "user1",
"lastName": "lastname",
"username": "user1lastname#domainname.com",
"playerId": 0,
"jwtToken": "token in is here",//hidden for security
"error": {
"eventName": null,
"errorMessage": null,
"errorDate": null,
"statusCode": null,
"json": null
},
"refreshToken": null
}
I typically send this to the end point from C sharp
{
"username": "user1#domain.com",
"password": "pass1"
}
What I found I had to do was this
let decoder = JSONDecoder()
let responseObject = try
decoder.decode(AuthenticationResponse.self, from: data)
print(responseObject)
And change my class to be off this
import Foundation
struct AuthenticationResponse: Codable {
var jwtToken: String
}
After I done that I got the expected string back but my question is how does one get this to run correctly its completing before I think I need await but also where is it best to stored the jwttoken?

Combine - how to proceed to decode a local json file if online fetch failed?

I have an up-to-date json file hosted online and a local json file in my Xcode workspace. I would like to proceeed to decode a locally stored file if fetching failed: MyError.fetchError e.g. for no internet connection. This is the pipeline:
func fetchAndDecode<T: Decodable>(url: URL) -> AnyPublisher<T, MyError> {
fetchURL(url: url)
.decode(type: T.self, decoder: JSONDecoder())
.mapError { error in
if let error = error as? DecodingError {
return MyError.parsingError
} else {
return MyError.fetchError //here somehow proceed to parse local json file
}
}
.eraseToAnyPublisher()
}
How to achieve this ?
.mapError is the wrong operator because it considers only the Error branch.
fetchURL returns obviously Data, so before decoding the data you have to replace the fetch error with the local data.
Before the .decode... line insert
.replaceError(with: try! Data(contentsOf: Bundle.main.url(forResource: "local", withExtension: "json")!))
and delete the .mapError operator.
local.json represents the file name of the local file in the bundle.
I can propose an alternate but similar method to download the data and handle the error, using the async functions introduced for iOS 15.
Create a function that reads the data asynchronously and returns the data from the server if the connection worked, otherwise it will return the local JSON if a problem was found:
func getData(fromURL url: URL) async -> Data {
let request = URLRequest(url: url)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("HTTP response: \(response.debugDescription)")
// Found an issue: return the local JSON
return localJSON
}
// If everything is OK, return the data from the server
return data
}
Decode the data returned:
// Use the code below in an asynchronous environment -
// either an async function or inside a Task { } closure
let data = await getData(fromURL: url)
do {
let decoded = try JSONDecoder().decode(T.self, from: data)
print("Decoded JSON: \(decoded)")
return decoded
} catch {
print("Error decoding JSON: \(error), \(error.localizedDescription)")
}

URLRequest - "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" Error (Swift)

I am trying to perform an HTTP POST request in swift that will send some data to my server using PHP file, but it crashes with the error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
The token and selectedAreaNames (the error is in the first line) are just regular strings. What could be the problem?
let url = URL(string: "https://xxxxxxx.xxx/register.php/\(token)|\ (selectedAreaNames)")! //error is here...
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error)")
} else {
if let response = response as? HTTPURLResponse {
print("statusCode: \(response.statusCode)")
}
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("data: \(dataString)")
}
}
}
task.resume()
Assuming that’s really how your URL must look, you can do:
let url = URL(string: "https://xxxxxxx.xxx/register.php")!
.appendingPathComponent(token + "|" + selectedAreasNames)
That will percent escape those portions of the URL (including the |).
That having been said, this is an exceedingly unusual format for a POST request, which usually has the data being posted inside the body of the request, not just added as another path component of the URL. And if this was a GET request, where the parameters are added to the URL, you’d generally see this after a ? in the URL, separating the path of the request from the query. And this structure of simply TOKEN|VALUES is an unusual query structure, too.

Pass an integer as a parameter in an Alamofire PUT request

So I am trying to do a PUT request using Alamofire and I need an integer parameter (not an object).
The request sends an id to the database and the query makes an update to an object with that id in the database.
The Parameters object in alamofire seems to take only objects:
var parameters: Parameters = ["key" : "value"]
is there any way to just send an integer without using the object?
The error I keep getting when I use that method above is:
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of int out of START_OBJECT token
and I am assuming this means I am passing an object when I should be passing an int instead.
This is my request :
Alamofire.request(url, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: nil).response{ response in
if response.response?.statusCode == 200 {
// pass
}else{
// fail
}
completionHandler((response.response?.statusCode)!)
}
I can't seem to find any examples that have to do with us online.
If you give more information about where you are sending the request, then I can test my solution to see if it works. But you can try this.
let url = "YOUR_URL"
let yourData = "WHATEVER CUSTOM VAL YOU WANT TO PASS"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "PUT"
//request.setValue("application/json", forHTTPHeaderField: "Content-Type") //This is if you want to have headers. But it doesn't look like you need them.
request.httpBody = yourData
/*
do {
request.httpBody = try JSONSerialization.data(withJSONObject: yourData)
} catch {
print("JSON serialization failed: \(error)")
}
*/
Alamofire.request(request).responseJSON {(response) in
if response.response?.statusCode == 200 {
print ("pass")
}else{
print ("fail")
}
completionHandler((response.response?.statusCode)!)
/*
switch response.result {
case .success(let value):
print ("success: \(value)")
case .failure(let error):
print("error: \(error)")
}
*/
}