Response validation Doesn't work properly in ktor - kotlin

So I have an API response like this :
If it succeed
{"success":true,"message":"user found","data": {data} }
If failed
{"success":false,"message":"user not found" }
the response code for the two above is 200.
After reading Ktor's documentation, we can use Response validation (https://ktor.io/docs/response-validation.html#2xx).
When I get a response with status = false, the validation is working properly.
But when I get response with status = true, I get this error
io.ktor.serialization.JsonConvertException: Illegal input
But if I don't use validationResponse, and get response API status = true everything is fine.
My code now
HttpResponseValidator {
validateResponse { response ->
val error: ErrorResponse = response.body()
if (!error.success) {
if (error.message.equals("bad_password", ignoreCase = true)) {
throw ResponseException.BadPassword
} else {
if (error.message.isEmpty()) {
throw ResponseException.BadRequest
} else {
throw ResponseException.UnknownError(
error = error.message
)
}
}
}
}
}
#Serializable
data class ErrorResponse(
#SerialName("message")
val message: String,
#SerialName("success")
val success: Boolean
)
is there any way to solve this ?

Related

Getting error while uploading image using multipart request in Alamofire

xcode 10.3
swift 4.2
iPhone app
I'm hitting a post type API to upload image along with some string data from a form. I'm getting response result as success but there is an error parameters which indicates: CredStore - performQuery - Error copying matching creds. Error=-25300
I have tried doing some research and found that I've problem with URLCredentialStorage but I don't have any Credentials in API.
I looked at CredStore Perform Query error, I'm having very similar issue but can't find my solution there.
{
let url = route.asURL(constants: constants)
let parameters = route.asParameters(constants: constants)
let headers: HTTPHeaders = [
"authorization-name": APISession.token,
"Content-Type": "multipart/form-data"
]
Log(request: URLRequest(url: url))
Alamofire.upload(multipartFormData: { (multipartFormData) in
for (key, value) in parameters {
if let strValue = value as? String, let valueData = strValue.data(using: .utf8) {
multipartFormData.append(valueData, withName: key)
}
if let imgValue = value as? UIImage, let imgData = imgValue.jpegData(compressionQuality: 0.5) {
multipartFormData.append(imgData, withName: key, mimeType: "image/jpg")
}
}
}, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers) { (result) in
print(result)
switch result{
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
print("Preogress is: ", progress.fractionCompleted)
})
upload.responseJSON { response in
print("Succesfully uploaded")
switch response.result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
case .failure(let error):
print("Error in upload: \(error.localizedDescription)")
}
}
}
I'm getting response like this,
success(request: 2019-08-12 13:07:04.224913-0400 project name[65508:460860] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = htps;
"r_Attributes" = 1;
sdmn = "url";
srvr = "url";
sync = syna;
}
so I'm confused why I'm getting success response with error parameters in that.
Not sure but I think authorization-name key present in headers is incorrect. It should be Authorization.

Display backend error message after Create call

In my controller I have the following code - The problem is when it is error, I want to display exact error that is being passed from backend. But in my error handling function I never get any data. What is wrong here?
Controller.js
var token = null;
$.ajax({
url: sServiceURl,
type: "GET",
async: true,
beforeSend: function(xhr) {
sap.ui.core.BusyIndicator.show(0);
xhr.setRequestHeader("X-CSRF-Token", "Fetch");
},
complete: function(xhr) {
token = xhr.getResponseHeader("X-CSRF-Token");
oContext.OdataModel.create("/materialsSet", oContext.Data, null, oContext.submitSuccess.bind(oContext), oContext.submitError.bind(oContext));
}
});
submitSuccess: function(data, response, oContext) {
// works fine
},
submitError: function(oError) {
// I never get anything in oError, so the below code is useless.
try {
if (oError.responseText) {
obj = JSON.parse(oError.responseText);
message = obj.error.message.value;
} else if (oError.response.body) {
var errorModel = new sap.ui.model.xml.XMLModel();
errorModel.setXML(oError.response.body);
//Read message node
if (errorModel.getProperty("/0/message") !== "") {
message = errorModel.getProperty("/0/message");
} else {
message = message1;
}
} else {
message = message1;
}
} catch (error) {
message = message1;
}
sap.m.MessageToast.show(message);
},
Hope this will help you, first of all check backend response. I have use the below code for error handling.
submitError: function(responseBody) {
try {
var body = JSON.parse(responseBody);
var errorDetails = body.error.innererror.errordetails;
if (errorDetails) {
if (errorDetails.length > 0) {
for (i = 0; i < errorDetails.length; i++) {
console.log(errorDetails[i].message);
}
} else
console.log(body.error.message.value);
} else
console.log(body.error.message.value);
} catch (err) {
try {
//the error is in xml format. Technical error by framework
switch (typeof responseBody) {
case "string": // XML or simple text
if (responseBody.indexOf("<?xml") > -1) {
var oXML = jQuery.parseXML(responseBody);
var oXMLMsg = oXML.querySelector("message");
if (oXMLMsg)
console.log(oXMLMsg.textContent);
} else
console.log(responseBody);
break;
case "object": // Exception
console.log(responseBody.toString());
break;
}
} catch (err) {
console.log("common error message");
}
}
}

Alamofire Decodable serializer

I am trying to update some code for a Alamofire custom response serializer I found this bit of code on "bits of cocoa".
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .AllowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
do {
return .Success(try [T].decode(value))
} catch {
return .Failure(Error.errorWithCode(.JSONSerializationFailed,
failureReason: "JSON parsing error, JSON: \(value)"))
}
case .Failure(let error): return.Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
This is pre swift 3, and Response<[T], NSError> is now a single value specialization Response<[T]> because of this I am not sure how this extension would translate for the changes to Alamofire on the swift 3
I started to update this code this is as far as I got
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: (Response<[T]>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T]> { request, response, data, error in
guard error == nil else { return .failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
return .success(try [T].decode(value))
} catch {
return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, com
pletionHandler: completionHandler)
}
}
this get me 2 errors that at the moment I have not found any way to fix them:
1) for "return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))", I am getting this error ('Error' cannot be constructed because it as no accessible initializers)
2) for "return response(responseSerializer: responseSerializer, completionHandler: completionHandler)", I am getting this error (Cannot call value of non-function type 'HTTPURLResponse')
Hopefully if any one can point me to a better solution then this bit of code or the correct fix for this. Thanks I will be working on this still, if I do fix it I will update this ticket.
Edit - update
So This is the code as of now
extension Alamofire.Request {
public func responseDecodable<T: Decodable>(completionHandler: #escaping (Response<T>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<T> { request, response, data, error in
guard error == nil else {
print("error Network request: \(error)")
return .failure(error!)
}
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
let decodableObject = try T.decode(value)
return .success(decodableObject)
} catch let decodeErr {
print(decodeErr)
let failureReason = "JSON parsing error, JSON: \(value)"
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
let error = NSError(domain: "com.prospects.error", code: BackendError.JSONSerializationFailed.rawValue, userInfo: userInfo)
return .failure(error)
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
public protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: AnyObject)
}
as per last comment : Response and ResponseSerializer are now unresolved, this used to work with no error yesterday. but updating xcode and alamofire this morning as made this to get errors now.

Get server response message from error

My server (CakePHP) is responding like so:
$this->response->statusCode('400');
$this->response->type('json');
$this->response->body(json_encode(array('message' => 'Bookmark already exists')));
The Postman output looks like what you would expect:
{"message":"Bookmark already exists"}
The problem is that I cannot find a way to access this message from the failure handler (Alamofire 3.1.3 + SwiftyJSON 2.3.2)
Alamofire.request(.POST...
.validate()
.responseJSON { response in
switch response.result {
case .Success(_):
// All good
case .Failure(let error):
// Status code 400
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result)
I cannot find a way to cast response.data to JSON as a I simply get nil and the result returns just FAILURE.
Is there a way to access this server message from the failure handler ?
The data is not parsed in the .Failure case per the Alamofire 3.0 migration guide. However, server data is still available in response.data and can be parsed.
Below should work to parse this manually:
Alamofire.request(.POST, "https://example.com/create", parameters: ["foo": "bar"])
.validate()
.responseJSON { response in
switch response.result {
case .Success:
print("Validation Successful")
case .Failure(_):
var errorMessage = "General error message"
if let data = response.data {
let responseJSON = JSON(data: data)
if let message: String = responseJSON["message"].stringValue {
if !message.isEmpty {
errorMessage = message
}
}
}
print(errorMessage) //Contains General error message or specific.
}
}
}
This uses SwiftyJSON which provides the JSON struct to convert NSData. Parsing NSData to JSON can done without SwiftyJSON, answered here.
Another cleaner option might be to write a Custom Response Serializer.
A method with the router and no SwiftyJSON:
Alamofire.request(APIRouter.Register(params: params)).validate().responseJSON { response in
switch response.result {
case .Success(let json):
let message = json["clientMessage"] as? String
completion(.Success(message ?? "Success"))
case .Failure(let error):
var errorString: String?
if let data = response.data {
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
errorString = json["error"]
}
}
completion(.Error(errorString ?? error.localizedDescription))
}
}
I have used the following lines to read the response body from a Alamofire request.
Alamofire.request(.POST, serveraddress, headers: headers, encoding: .JSON)
.response{ request, response, data, error in
let responseData = String(data: data!, encoding: NSUTF8StringEncoding)
print(responseData)
}
With this body I can get my custom server response errormessage.
best regards
For Alamofire 4.0 and above :
Try this
response.response?.statusCode
url : "YOUR-URL"
parameters: "YOUR PARAMETER DICTIONARY"
headers: "YOUR HEADER DICTIONARY"
I am using SwiftyJSON for JSON Parsing
A sample request is here :
func createPostRequestWith(path: String?,
parameters: [String : Any]? = nil,
success : #escaping (Any?) -> (),
failure : #escaping (NSError) -> ()) {
if !(Alamofire.NetworkReachabilityManager()?.isReachable)! {
let error = NSError(domain: "", code: -1003, userInfo: nil)
failure(error)
} else {
guard let url = path else { return }
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: createCurrentHeader()).validate(statusCode: 200..<300).responseJSON {
response in
switch response.result {
//Remove loader here either after parsing or on error
case .success(let data):
success(data)
case .failure(let error):
print(error)
if let responseData = response.data {
var parsedResponseData = JSON.init(data: responseData)
let customError = NSError(domain: parsedResponseData["message"].stringValue, code: response.response?.statusCode ?? 555, userInfo: nil)
failure(customError as NSError)
} else {
failure(error as NSError)
}
}
}
}
}

Parse Cloud Code query not getting executed

I have the below cloud code function and when I call this function from my OS X app, I get the success response as well. But none of the console log output messages inside the success and failure blocks of the query operation gets executed. Any ideas on where to look would be much appreciated.
Parse.Cloud.define("markAlertAsExpired", function(request, response) {
Parse.Cloud.useMasterKey();
var Alert = Parse.Object.extend("Alert");
var query = new Parse.Query(Alert);
query.get("vC6ppoxuqd", {
success: function(alertObj) {
// The object was retrieved successfully.
var status = alertObj.get("status");
console.log("RECEIVED OBJECT WITH STATUS:");
console.log(status);
if (status == "active") {
console.log("active");
markActiveAlertAsExpired(alertObj);
} else if (status == "inactive") {
console.log("inactive");
markInactiveAlertAsExpired(alertObj);
} else {
console.error("unknown_status");
}
},
error: function(object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
console.error("alert_not_found");
response.error("alert_not_found");
}
});
response.success("available");
});
You need to wait for your queries to complete before calling response.success, the updated code below should work.
Parse.Cloud.define("markAlertAsExpired", function(request, response) {
Parse.Cloud.useMasterKey();
var Alert = Parse.Object.extend("Alert");
var query = new Parse.Query(Alert);
query.get("vC6ppoxuqd", {
success: function(alertObj) {
// The object was retrieved successfully.
var status = alertObj.get("status");
console.log("RECEIVED OBJECT WITH STATUS:");
console.log(status);
if (status == "active") {
console.log("active");
markActiveAlertAsExpired(alertObj);
} else if (status == "inactive") {
console.log("inactive");
markInactiveAlertAsExpired(alertObj);
} else {
console.error("unknown_status");
}
response.success("available");
},
error: function(object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
console.error("alert_not_found");
response.error("alert_not_found");
}
});
});