Convert Firebase Firestore API POST request from CURL to Swift URLRequest - swift

I have a simple cURL request which inserts data into Firestore database. This works, and no authentication is needed. I need to use cURL as no Firestore library is available for watchOS.
curl -X POST -H "Content-Type: application/json" -d '
{
"fields": {
"Field1": {
"stringValue": "'"$var12"'"
},
"Field2": {
"stringValue": "'"$var22"'"
},
"Field3": {
"stringValue": "$var32"
}
}
}' "https://firestore.googleapis.com/v1/projects/project-a9c7/databases/(default)/documents/TestRuns/3001/MyRuns"
However, when I try to rewrite the request using URLRequest to use it in watchOS SwiftUI App, the app returns an error.
The previous answer did not help me.
ERROR:
The code is 404 not found, but the same URL works from terminal.
statusCode should be 2xx, but is 404
response = <NSHTTPURLResponse: 0x6000021482c0> { URL: https://firestore.googleapis.com/v1/projects/runny-a9c7/databases/(default)/documents/TestRuns/3001/MyRuns } { Status Code: 404, Headers {
"Alt-Svc" = ( ...
If I use PATCH instead of PUT as suggested, the response is 400, and the code still doesn't create new record in database.
The URLRequest call, which I tried running from SwiftUI Playground and also watchOS App:
import Foundation
// CREDIT: https://stackoverflow.com/questions/63530589/using-post-and-auth-with-firebase-database-and-swift
extension Dictionary {
func percentEncoded() -> Data? {
return map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
.data(using: .utf8)
}
func percentEncodedString() -> String? {
return map { key, value in
let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
return escapedKey + "=" + escapedValue
}
.joined(separator: "&")
}
}
class Body: Codable {
var name: String
init(name: String){
self.name = name
}
}
extension CharacterSet {
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
return allowed
}()
}
let url = "https://firestore.googleapis.com/v1/projects/runny-a9c7/databases/(default)/documents/TestRuns/3001/MyRuns"
var request = URLRequest(url: URL(string: url)!)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "PUT"
let parameters: [String: Any] = [
"field1": "A",
"field2": "B"
]
request.httpBody = parameters.percentEncoded()
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("App error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else { // check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
}
task.resume()
Do you have any idea, when went wrong here, and how to fix the problem?
Thanks for any help.

I needed to use the POST method:
request.httpMethod = "POST"
Then , I adjusted the parameters:
let parameters: [String:[String:[String: String]]] = [
"fields": ["Field1": ["stringValue": "val"]]
]
Finally, I sent the JSON data using the JSONSerialization class.
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
The data got successfully written to Firestore database.
Thanks so much Larme!.

Related

how to pass output of HTTP request Task to variable in Swift

In Chrome browser, I input this address
https://jsonplaceholder.typicode.com/todos/1
Then I see output on Chrome window:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
Now my question is that, can i do this in Swift?
Accessing an http address then get the output?
Now on this webpage, i find the below code.
import SwiftUI
// Create URL
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")
guard let requestUrl = url else { fatalError() }
// Create URL Request
var request = URLRequest(url: requestUrl)
// Specify HTTP Method to use
request.httpMethod = "GET"
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
// Convert HTTP Response Data to a simple String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
}
}
task.resume()
I run this code in Playground in XCode. As it says on the webpage I got the right output:
Response data string:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
But it prints on the console of XCode. I cannot find a way to pass this output to a variable. So I cannot go further.
I tried add lines like:
return dataString
all got error.
Or put it in a Func(), then call the function, also got error.
I am not a programmer and very new to Swift, hope people here can help.
You need to decode from JSON and store the result in an object.
Create an object that conforms to Codable and can store the result - it need to have all the properties of the JSON:
struct MyObject: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
Create a function to decode:
func decode(data: Data) -> MyObject? {
do {
let result = try JSONDecoder().decode(MyObject.self, from: data)
return result
} catch {
print("\n-->> Error decoding JSON: \(error), \(error)")
return nil
}
}
Use the function:
if let data = data {
// downloaded will store the result from the JSON
let downloaded = decode(data: data)
print(downloaded) // Optional(Test_Swift.MyObject(userId: 1, id: 1, title: "delectus aut autem", completed: false))
print(downloaded?.title ?? "Could not decode") // delectus aut autem
}

Ximilar POST request with image URL and headers

I am trying to run a post request to send an image to the ximilar app. Not even sure if my request is 100% correct. Each time I get the same response of:
https://api.ximilar.com/recognition/v2/classify/ } { Status Code: 400, Headers {
"Content-Type" = (
"application/json"
);
Date = (
"Mon, 12 Apr 2021 04:02:58 GMT"
);
Server = (
"nginx/1.16.1"
);
"Strict-Transport-Security" = (
"max-age=31536000"
);
Vary = (
"Accept, Origin"
);
allow = (
"POST, OPTIONS"
);
"referrer-policy" = (
"same-origin"
);
"x-content-type-options" = (
nosniff
);
"x-frame-options" = (
DENY
);
} }
{
records = (
"Expected a list of items but got type \"str\"."
);
}
My code: (not sure if the body values are written right.)
let url = URL(string: "https://api.ximilar.com/recognition/v2/classify/")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Token __MyTOKEN__", forHTTPHeaderField: "Authorization")
let body = [
"task_id" : "c03c288b-a249-4b17-9f63-974c2f30beb9",
"records" : "https://www.sticky.digital/wp-content/uploads/2013/11/img-6.jpg"
]
let bodyData = try? JSONSerialization.data(withJSONObject: body, options: [] )
request.httpMethod = "POST"
request.httpBody = bodyData
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
I want to be able to output the JSON response in the console.
Here is the code in curl which works fine:
curl -H "Content-Type: application/json" -H "authorization: Token __API_TOKEN__" https://api.vize.ai/v2/classify -d '{"task_id": "0a8c8186-aee8-47c8-9eaf-348103feb14d", "version": 2, "descriptor": 0, "records": [ {"_url": "__IMAGE URL HERE__" } ] }'
The server is reporting an error for records:
Expected a list of items but got type "str"
It is saying that it was expecting an array of items for records, but only received a string. In your curl, records is an array of dictionaries with a single key, _url, but in Swift the value is just a string, consistent with what the error reported. You also supply version and descriptor in curl, but not in the Swift example.
Thus, you might try:
let body: [String: Any] = [
"task_id" : "c03c288b-a249-4b17-9f63-974c2f30beb9",
"version": 2,
"descriptor": 0,
"records" : [
[
"_url": "https://www.sticky.digital/wp-content/uploads/2013/11/img-6.jpg"
]
]
]
Alternatively, you might define Encodable types as outlined in Encoding and Decoding Custom Types:
struct XimilarRequest: Encodable {
let taskId: String
let version: Int
let descriptor: Int
let records: [XimilarRecord]
}
struct XimilarRecord: Encodable {
let url: URL
enum CodingKeys: String, CodingKey {
case url = "_url"
}
}
Then you can do:
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let imageUrl = URL(string: "https://www.sticky.digital/wp-content/uploads/2013/11/img-6.jpg")!
let ximilarRequest = XimilarRequest(
taskId: "c03c288b-a249-4b17-9f63-974c2f30beb9",
version: 2,
descriptor: 0,
records: [XimilarRecord(url: imageUrl)]
)
request.httpBody = try encoder.encode(ximilarRequest)
Hi here is the example that should work for you (fill the Auth token and _url of the image):
import Foundation
let headers = [ "Content-Type": "application/json", "authorization": "Token __API_TOKEN__" ] let parameters = [ "task_id": "0a8c8186-aee8-47c8-9eaf-348103feb14d", "version": 2, "records": [["_url": "__IMAGE URL HERE__"]] ] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://api.ximilar.com/recognition/v2/classify")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data
let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) {
print(error) } else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse) } })
dataTask.resume()

Get "Parse Error" when sending request to YouTube Data API to start a resumable upload session

https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
I try to follow this guide to start a resumable session.
But server returns this response :
success({
error = {
code = 400;
errors = (
{
domain = global;
message = "Parse Error";
reason = parseError;
}
);
message = "Parse Error";
};
})
Swift 5/ Alamofire 5
let filePath = "/Users/lucas/Documents/test.mp4"
do{
let attr = try FileManager.default.attributesOfItem(atPath: filePath)
let dict = attr as NSDictionary
let fileSize = dict.fileSize()
let dicSnippest = ["title": "TestVideo",
"description": "_description_",
"tags":"gundam",
"catagoryId":"22"]
let dicStatus = ["privacyStatus": "private",
"embedded": "True",
"license": "youtube"]
let sessionParam = ["snippet": dicSnippest,
"status": dicStatus
]
let targetUrl = "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status"
let sessionHeaders = HTTPHeaders([
"Authorization": "Bearer \(token)",
"Content-Type": "application/json; charset=utf-8",
"Content-Length": "\(fileSize)",
"X-Upload-Content-Length": "\(fileSize)",
"X-Upload-Content-Type": "application/octet-stream"])
print("token: \(token)")
print("\(filePath): \(fileSize) bytes")
AF.request(targetUrl, method: .post, parameters: sessionParam, encoding: URLEncoding.default, headers: sessionHeaders).responseJSON {
response in
print("Http Status Code: \(String(describing: response.response?.statusCode))")
switch (response.result)
{
case .success(_):
print("Get Server Response!!")
print(response.result)
break
case .failure(let error):
print("Failure!!")
print(error)
break
}
}
} catch {
}
BTW, I modify the parameter of AF.request - encoding from URLEncoding.httpBody to JSONEncoding.default.
This error is happened:
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.inputDataNilOrZeroLength)

Parse responseJSON to ObjectMapper

I'm currently making a migration from Android to iOS, better said Java to Swift, I got a generic response in JSON, but I'm not able to use it as an object and show it in the storyboard. I'm really new to Swift so I've been stuck for a while.
I've tried ObjectMapper and also JSON decode with no result at all.
I declared this response as I used in Java(Android)
class ResponseObjectMapper<T,R>: Mappable where T: Mappable,R:Mappable{
var data:T?
var message:String!
var error:R?
required init?(_ map: Map) {
self.mapping(map: map)
}
func mapping(map: Map) {
data <- map["data"]
message <- map["message"]
error <- map["error"]
}
}
class UserMapper :Mappable{
var email:String?
var fullName:String?
var id:CLong?
var phoneNumber:String?
var token:CLong?
required init?(_ map: Map) {
}
func mapping(map: Map) {
email <- map["email"]
fullName <- map["fullName"]
id <- map["id"]
phoneNumber <- map["phoneNumber"]
token <- map["token"]
phoneNumber <- map["phoneNumber"]
}
}
In my Android project I use the Gson dependency and I was able to use my JSON as an object
class ErrorMapper:Mappable{
var message:String?
var code:Int?
required init?(_ map: Map) {
}
func mapping(map: Map) {
message <- map["message"]
code <- map["code"]
}
}
This is the Alamofire that gave me the JSON.
func login(params: [String:Any]){Alamofire.request
("http://192.168.0.192:8081/SpringBoot/user/login", method: .post,
parameters: params,encoding: JSONEncoding.default, headers:
headers).responseJSON {
response in
switch response.result {
case .success:
let response = Mapper<ResponseObjectMapper<UserMapper,ErrorMapper>>.map(JSONString: response.data)
break
case .failure(let error):
print(error)
}
}
}
If I print the response with print(response) I got
SUCCESS: {
data = {
email = "vpozo#montran.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
};
error = "<null>";
message = SUCCESS;
}
and if I use this code I can got a result with key and value but I don't know how to use it as an object
if let result = response.result.value {
let responseDict = result as! [String : Any]
print(responseDict["data"])
}
console:
Optional({
email = "vpozo#gmail.com";
fullName = "Victor Pozo";
id = 6;
phoneNumber = 099963212;
token = 6;
})
I would like to use it in an Object, like user.token in a View Controller, probably I'm really confused, trying to map with generic attributes.
Type 'ResponseObjectMapper<UserMapper, ErrorMapper>' does not conform to protocol 'BaseMappable'
First of all you will need a Network Manager which uses Alamofire to make all your requests. I have made generalized one that looks something like this. You can modify it as you want.
import Foundation
import Alamofire
import SwiftyJSON
class NetworkHandler: NSObject {
let publicURLHeaders : HTTPHeaders = [
"Content-type" : "application/json"
]
let privateURLHeaders : HTTPHeaders = [
"Content-type" : "application/json",
"Authorization" : ""
]
enum RequestType {
case publicURL
case privateURL
}
func createNetworkRequestWithJSON(urlString : String , prametres : [String : Any], type : RequestType, completion:#escaping(JSON) -> Void) {
let internetIsReachable = NetworkReachabilityManager()?.isReachable ?? false
if !internetIsReachable {
AlertViewManager.sharedInstance.showAlertFromWindow(title: "", message: "No internet connectivity.")
} else {
switch type {
case .publicURL :
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: publicURLHeaders)
break
case .privateURL:
commonRequest(urlString: baseURL+urlString, parameters: prametres, completion: completion, headers: privateURLHeaders)
break
}
}
}
func commonRequest(urlString : String, parameters : [String : Any], completion : #escaping (JSON) -> Void , headers : HTTPHeaders){
print("urlString:"+urlString)
print("headers:")
print(headers)
print("parameters:")
print(parameters)
let url = NSURL(string: urlString)
var request = URLRequest(url: url! as URL)
request.httpMethod = "POST"
request.httpHeaders = headers
request.timeoutInterval = 10
let data = try! JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted)
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if let json = json {
print("parameters:")
print(json)
}
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue)
let alamoRequest = AF.request(request as URLRequestConvertible)
alamoRequest.validate(statusCode: 200..<300)
alamoRequest.responseJSON{ response in
print(response.response?.statusCode as Any )
if let status = response.response?.statusCode {
switch(status){
case 201:
print("example success")
SwiftLoader.hide()
case 200 :
if let json = response.value {
let jsonObject = JSON(json)
completion(jsonObject)
}
default:
SwiftLoader.hide()
print("error with response status: \(status)")
}
}else{
let jsonObject = JSON()
completion(jsonObject)
SwiftLoader.hide()
}
}
}
}
After this when ever you need to make a request you can use this function. This will take in parameters if any needed and once the request is complete it will execute a call back function in which you can handle the response. The response here will be of SWIFTYJSON format.
func makeNetworkRequest(){
let networkHandler = NetworkHandler()
var parameters : [String:String] = [:]
parameters["email"] = usernameTextField.text
parameters["pass"] = passwordTextField.text
networkHandler.createNetworkRequestWithJSON(urlString: "http://192.168.0.192:8081/SpringBoot/user/login", prametres: parameters, type: .publicURL, completion: self.handleResponseForRequest)
}
func handleResponseForRequest(response: JSON){
if let message = response["message"].string{
if message == "SUCCESS"{
if let email = response["data"]["email"].string{
//Do something with email.
}
if let fullName = response["data"]["fullName"].string{
//Do something with fullName.
}
if let id = response["data"]["id"].int{
//Do something with id.
}
if let phoneNumber = response["data"]["phoneNumber"].int64{
//Do something with phoneNumber.
}
if let token = response["data"]["token"].int{
//Do something with token.
}
}else{
//Error
}
}
}
Hope this helps. Let me know if you get stuck anywhere.

How to write GraphQL Query

I have a working web graphql query as :
{
me{
... on Student{
profile {
fullName
emailId
mobileNumber
civilId
address
city
state
country
zipCode
userProfilePic
userCategory
createdAt
updatedAt
}
}
}
}
It returns the profile details of a particular student. I log using mutation and gets the token for a user.
I want to create a graphql file (ex. StudentProfile.graphql) in order to make fetch request (similar to http. get) using Apollo client.
I make this request to fetch the graphql query.
func fetchStudentProfileDetails(){
let tokenString = "Bearer " + "....my token ..."
print(tokenString)
let newApollo: ApolloClient = {
let configuration = URLSessionConfiguration.default
// Add additional headers as needed
configuration.httpAdditionalHeaders = ["Authorization": tokenString]
let url = URL(string: "http://52.88.217.19/graphql")!
return ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration))
}()
newApollo.fetch(query: StudentProfileQuery()) { (result, error) in
self.profileDetailsTextView.text = "Success"
if let error = error {
NSLog("Error while fetching query: \(error.localizedDescription)");
self.profileDetailsTextView.text = error.localizedDescription
}
guard let result = result else {
NSLog("No query result");
self.profileDetailsTextView.text = "No query result"
return
}
if let errors = result.errors {
NSLog("Errors in query result: \(errors)")
self.profileDetailsTextView.text = String(describing: errors)
}
guard let data = result.data else {
NSLog("No query result data");
return
}
}
}
How do I convert the following web query into a query in the .graphql file?
so, you can call to create new document into Graphql server using a simple NSUrlSession
let headers = ["content-type": "application/json"]
let parameters = ["query": "mutation { createProfile(fullName: \"test name\" emailId: \"test#email.com\") { id } }"] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://<url graphql>")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
I'm not sure I completely understand your question, but you should be able to use HTTP to make queries. For most people, a *.gql file just contains the query as a String which they URLEncode. Below is an example of reading from a variable but you could do just the same reading the query from a file as a string/buffer.
const myQuery = `{
user {
name
}
}`;
const queryURL = "http://52.88.217.19/graphql/?query=" + URLEncode(myQuery);
fetch(queryURL)
.then((result) => {
console.log(result);
})
If this does not answer your question, please help me better understand what you are asking, and I will try to revise my answer.