Put request failing with Alamofire 2.0 - swift

I have recently upgraded to Alamofire 2.0, and now my Put request is failing with a 400 error, when it was previously working properly. I perform the call with the code:
Alamofire.request(Router.Put(query: url, params: params, encoding: .JSON))
.validate()
.responseJSON() {
(request, response, result) in
print("request: \(request)")
print("response: \(response)")
print("result: \(result)")
switch result {
case .Success(_):
// success
case .Failure(let data, _):
// error occured
}
}
and my custom Router class:
enum Router: URLRequestConvertible {
case Get(query: String, params: [String: AnyObject]?)
case Post(query: String, params: [String: AnyObject]?)
case Put(query: String, params: [String: AnyObject]?, encoding: ParameterEncoding)
case Delete(query: String, params: [String: AnyObject]?)
var URLRequest: NSMutableURLRequest {
var encodeMethod: Alamofire.ParameterEncoding = Alamofire.ParameterEncoding.URL
// Default to GET
var httpMethod: String = Alamofire.Method.GET.rawValue
let (path, parameters): (String, [String: AnyObject]?) = {
switch self {
case .Get(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.GET.rawValue
// Return the query
return (query, params)
case .Post(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.POST.rawValue
// Return the query
return (query, params)
case .Put(let query, let params, let encoding):
// Set the request call
httpMethod = Alamofire.Method.PUT.rawValue
// Set the encoding
encodeMethod = encoding
// Return the query
return (query, params)
case .Delete(let query, let params):
// Set the request call
httpMethod = Alamofire.Method.DELETE.rawValue
// Return the query
return (query, params)
}
}()
// Create the URL Request
let URLRequest = NSMutableURLRequest(URL: NSURL(string: Globals.BASE_URL + path)!)
// set header fields
if let key = NSUserDefaults.standardUserDefaults().stringForKey(Globals.NS_KEY_SESSION) {
URLRequest.setValue(key, forHTTPHeaderField: "X-XX-API")
}
// Add user agent
if let userAgent = NSUserDefaults.standardUserDefaults().stringForKey(Globals.NS_KEY_USER_AGENT) {
URLRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent")
}
// Set the HTTP method
URLRequest.HTTPMethod = httpMethod
URLRequest.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
return encodeMethod.encode(URLRequest, parameters: parameters).0
}
}
Instead of the call being a success, the response is:
response: Optional(<NSHTTPURLResponse: 0x7fcee15b34d0> { URL: https://apiurl } { status code: 400, headers {
"Cache-Control" = "no-cache";
"Content-Length" = 26;
"Content-Type" = "application/json; charset=utf-8";
Date = "Tue, 15 Sep 2015 15:33:50 GMT";
Expires = "-1";
Pragma = "no-cache";
Server = "Microsoft-IIS/8.5";
} })
I looked into the problem and on the server side, Content-Type is coming in as blank for the request, when it should be coming in as application/json. The Content-Type should automatically get added when there is body data in the request. I have the params set:
// Save the profile
var params: [String: AnyObject] = ["indexPhoto": userProfile.indexPhoto,
"dob": df.stringFromDate(userProfile.dob) as NSString,
"identAs": userProfile.identAs]
// Add manually since creating the dictionary all at once is too much for swift to handle
params.updateValue(String(format:"%.2f", userProfile.heightIn), forKey: "heightIn")
params.updateValue(String(format:"%.2f", userProfile.weightLbs), forKey: "weightLbs")
params.updateValue(userProfile.eyes, forKey: "eyes")
params.updateValue(userProfile.hair, forKey: "hair")
...
Is there something I could be missing with this? Before I upgraded to Alamofire 2.0 this call was working just fine.

Maybe there is a different way but I fixed the same issue by sending empty params instead of nil. I don't use any parameters when using .PUT and because of that server doesn't know how to encode request and response (even if I explicitly set content type inside header, I receive blank content type on the server) so my solution was sending empty params like in the code below. Hope it helps someone.
var url = https://api.mysite.com/v1/issue/369613/delete
let params = ["":""]
let headers = NetworkConnection.addAuthorizationHeader(token, tokenType: tokenType)
manager.request(.PUT, url, parameters: params, encoding: .JSON, headers: headers)
.responseJSON { response in
if let JSONdata = response.result.value {
print("JSONdata: \(JSONdata)")
}
}

Related

Dependency Injection in Protocol/Extension

I am following along with this tutorial in order to create an async generic network layer. I got the network manager working correctly.
https://betterprogramming.pub/async-await-generic-network-layer-with-swift-5-5-2bdd51224ea9
As I try to implement more APIs, that I can use with the networking layer, some of the APIs require different tokens, different content in the body, or header etc, that I have to get at runtime.
In the snippet of code below from the tutorial, I get that we are building up the Movie endpoint based on .self, and then return the specific values we need. But the issue is, some of the data in this, for example, the access token, has to be hard coded here. I am looking for a way, that I can 'inject' the accessToken, and then it will be created with this new token. Again, the reason for this, is that in other APIs, the access token might not always be known.
protocol Endpoint {
var scheme: String { get }
var host: String { get }
var version: String? { get }
var path: String { get }
var method: RequestMethod { get }
var queryItems: [String: String]? { get }
var header: [String: String]? { get }
var body: [String: String]? { get }
}
extension MoviesEndpoint: Endpoint {
var path: String {
switch self {
case .topRated:
return "/3/movie/top_rated"
case .movieDetail(let id):
return "/3/movie/\(id)"
}
}
var method: RequestMethod {
switch self {
case .topRated, .movieDetail:
return .get
}
}
var header: [String: String]? {
// Access Token to use in Bearer header
let accessToken = "insert your access token here -> https://www.themoviedb.org/settings/api"
switch self {
case .topRated, .movieDetail:
return [
"Authorization": "Bearer \(accessToken)",
"Content-Type": "application/json;charset=utf-8"
]
}
}
var body: [String: String]? {
switch self {
case .topRated, .movieDetail:
return nil
}
}
For an example, I tried converting the var body to a function, so I could do
func body(_ bodyDict: [String, String]?) -> [String:String]? {
switch self{
case .test:
return bodyDict
}
The idea of above, was that I changed it to a function, so I could pass in a dict, and then return that dict in the api call, but that did not work. The MoviesEnpoint adheres to the extension Endpoint, which then gives the compiler error 'Protocol Methods must not have bodies'.
Is there a way to dependency inject runtime parameters into this Extension/Protocol method?
Change the declaration of MoviesEndpoint so that it stores the access token:
struct MoviesEndpoint {
var accessToken: String
var detail: Detail
enum Detail {
case topRated
case movieDetail(id: Int)
}
}
You'll need to change all the switch self statements to switch detail.
However, I think the solution in the article (four protocols) is overwrought.
Instead of a pile of protocols, make one struct with a single function property:
struct MovieDatabaseClient {
var getRaw: (MovieEndpoint) async throws -> (Data, URLResponse)
}
Extend it with a generic method to handle the response parsing and decoding:
extension MovieDatabaseClient {
func get<T: Decodable>(
endpoint: MovieEndpoint,
as responseType: T.Type = T.self
) async throws -> T {
let (data, response) = try await getRaw(endpoint)
guard let response = response as? HTTPURLResponse else {
throw URLError(.badServerResponse)
}
switch response.statusCode {
case 200...299:
break
case 401:
throw URLError(.userAuthenticationRequired)
default:
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(responseType, from: data)
}
}
Provide a “live“ implementation that actually sends network requests:
extension MovieDatabaseClient {
static func live(host: String, accessToken: String) -> Self {
return .init { endpoint in
let request = try liveURLRequest(
host: host,
accessToken: accessToken,
endpoint: endpoint
)
return try await URLSession.shared.data(for: request)
}
}
// Factored out in case you want to write unit tests for it:
static func liveURLRequest(
host: String,
accessToken: String,
endpoint: MovieEndpoint
) throws -> URLRequest {
var components = URLComponents()
components.scheme = "https"
components.host = host
components.path = endpoint.urlPath
guard let url = components.url else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.allHTTPHeaderFields = [
"Authorization": "Bearer \(accessToken)",
"Content-Type": "application/json;charset=utf-8",
]
return request
}
}
extension MovieEndpoint {
var urlPath: String {
switch self {
case .topRated: return "/3/movie/top_rated"
case .movieDetail(id: let id): return "/3/movie/\(id)"
}
}
}
To use it in your app:
// At app startup...
let myAccessToken = "loaded from UserDefaults or something"
let client = MovieDatabaseClient.live(
host: "api.themoviedb.org",
accessToken: myAccessToken
)
// Using it:
let topRated: TopRated = try await client.get(endpoint: .topRated)
let movieDetail: MovieDetail = try await client.get(endpoint: .movieDetail(id: 123))
For testing, you can create a mock client by providing a single closure that fakes the network request/response. Simple examples:
extension MovieDatabaseClient {
static func mockSuccess<T: Encodable>(_ body: T) -> Self {
return .init { _ in
let data = try JSONEncoder().encode(body)
let response = HTTPURLResponse(
url: URL(string: "test")!,
statusCode: 200,
httpVersion: "HTTP/1.1",
headerFields: nil
)!
return (data, response)
}
}
static func mockFailure(_ error: Error) -> Self {
return .init { _ in
throw error
}
}
}
So a test can create a mock client that always responds with a TopRated response like this:
let mockTopRatedClient = MovieDatabaseClient.mockSuccess(TopRated(...))
If you want to learn more about this style of dependency management and mocking, Point-Free has a good (but subscription required) series of episodes: Designing Dependencies.

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.

`Extra argument method in call` in Alamofire

I am trying to send the following data using Alamofire version 3 and Swift 3. And the error that I get is Extra argument method in call.
Here is what I have done so far:
struct userGoalServerConnection {
let WeightsUserGoal_POST = "http://api.umchtech.com:3000/AddWeightsGoal"
}
struct wieghtsUserGoal {
let id : Int
let token : String
let data : [ String : String ]
}
func syncInitialUserGaolValuesWithServer (userID : Int , userToken : String ){
let serverConnection = userGoalServerConnection()
let weightValue = wieghtsUserGoal.init(id: userID,
token: userToken,
data: ["weight_initial":"11","weight_end":"11","weight_difference":"11","calories_deficit":"11","difficulties":"11","weight_loss_week":"11","start_date":"2016-12-12","end_date":"2016-12-23","days_needed":"11"])
Alamofire.request(userGoalServerConnection.WeightsUserGoal_POST, method:.post, parameters: weightValue, encoding: JSONEncoding.default, headers:nil).responseJSON { response in
print(response.request as Any) // original URL request
print(response.response as Any) // URL response
print(response.result.value as Any) // result of response serialization
}
I am quite new to this so please dont mind if my question is a little bit too noob. I have gone through similar questions here but they did not help me out figuring where am i making a mistake :)
You are passing weightValue as POST param in request, it can't be. If you are using JSONEncoding then pass type of Parameters object.
let params: Parameters = [
"Key1": "value1",
"key2": "value2"
]
You can only pass dictionary type a alamofire request, you cannot post a struct type.
you can send [String : String], [String : Any] etc in a Alamofire request
You can try this way out
let WeightsUserGoal_POST = "http://api.umchtech.com:3000/AddWeightsGoal"
func Serialization(object: AnyObject) -> String{
do {
let stringData = try JSONSerialization.data(withJSONObject: object, options: [])
if let string = String(data: stringData, encoding: String.Encoding.utf8){
return string
}
}catch _ {
}
return "{\"element\":\"jsonError\"}"
}
func syncInitialUserGaolValuesWithServer (userID : Int , userToken : String ){
let data = ["weight_initial":"11","weight_end":"11","weight_difference":"11","calories_deficit":"11","difficulties":"11","weight_loss_week":"11","start_date":"2016-12-12","end_date":"2016-12-23","days_needed":"11"] as! [String : String]
let dataSerialized = Serialization(object: data as AnyObject)
let param = ["id" : userID,
"token" : userToken,
"data" : dataSerialized ]
Alamofire.request(WeightsUserGoal_POST, method: .post, parameters: param ,encoding: JSONEncoding.default, headers: nil).responseJSON { response in
print(response.request as Any) // original URL request
print(response.response as Any) // URL response
print(response.result.value as Any) // result of response serialization
}
}

Running Two Alamofire Requests in Sequence Swift

I am using Async library found here in order to wait for first alamofire request to complete before running my second from the ViewDidLoad. The library seems simple to use but I can never get the first request to wait. My code is as follows:
let group = AsyncGroup()
group.utility
{
self.getToken()
}
group.wait()
self.getDevices()
I would like the getToken function to complete the Alamofire request and its completion handler before moving on to the getDevices request. Both are very simple Alamofire requests.
EDIT:
This is the getToken request. The token variable is not getting updated with the alamofire response before second alamofire request is being called.
func getToken()
{
let httpheader: HTTPHeaders =
[
"Content-Type": "application/json"
]
// Dev
let param = [params here]
Alamofire.request("url", method: .post, parameters: param, encoding: JSONEncoding.default, headers:httpheader).validate()
.responseData { response in
switch response.result {
case .success:
if let data = response.data {
let xml = SWXMLHash.parse(data)
token = (xml["authResponse"] ["authToken"].element?.text)!
}
case .failure:
print ("error")
}
}
}
Your getToken looks more like:
func getToken(whenDone:(String?)->()) {
let httpheader: HTTPHeaders = [
"Content-Type": "application/json"
]
// Dev
let param = [params here]
Alamofire.request("url", method: .post, parameters: param, encoding: JSONEncoding.default, headers:httpheader).validate()
.responseData { response in
switch response.result {
case .success:
if let data = response.data {
let xml = SWXMLHash.parse(data)
token = (xml["authResponse"] ["authToken"].element?.text)!
whenDone(token)
}
case .failure:
print ("error")
whenDone(nil)
}
}
}
and the calling sequence just becomes:
getToken() { token ->
guard let token = token else {
return
}
getDevices()
}

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.