swiftcurlgoogle-cloud-firestoreurlrequest

Convert Firebase Firestore API POST request from CURL to Swift URLRequest


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.


Solution

  • 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!.