I need to catch a header datum(response.headers().get("x-rate-limit-remaining") in Response to validate it. The following solution is working, but It is ugly. Instead of this, I want to write ResponseInterceptor to catch the header rate-limit datum.
How can I do it?
Response response = myService.callMethod(request);
// for catching header data
Integer rateLimit = Integer.valueOf(response.headers().get("x-rate-limit-remaining").stream().findFirst().orElse("0"));
// For getting body
Decoder.Default decoder = new Decoder.Default();
CustomResponse customResponse;
try {
String body = (String) decoder.decode(response, String.class);
ObjectMapper mapper = new ObjectMapper();
customResponse = mapper.readValue(body, CustomResponse.class);
} catch (Exception e) {
log.error("Error occured during realty transfer response decoding. Error: {}", e.getMessage());
return;
}
Many thanks.
Related
This question already has answers here:
Alamofire Response Serialization Failed
(2 answers)
Closed 6 months ago.
I have an API where I PUT stuff to. I need to make sure to wait until I get an http 200 response from the server, but I don't know how to await that using Alamofire because my response itself if empty. So it's just an http 200 with no content.
I only can find async functions that e.g. serialize a String or Data or a Decodable, but they don't work if my response is empty.
Is there a way to await something like that in Alamofire?
I know that your question is about async/await from Alamofire, but is good to know that the http status codes 204 and 205 are exactly for this. Which means that if you have access to the server code you could send the empty responses with the http status code 204 and 205 instead of 200 and then Alamofire would not generate any errors. But assuming you don't have access to the server code and you need to parse an empty response as correct then you could use the following code:
func testRequestWithAlamofire() {
let dataResponseSerializer = DataResponseSerializer(emptyResponseCodes: [200, 204, 205]) // Default is [204, 205] so add 200 too :P
AF.request("http://www.mocky.io/v2/5aa696133100001335e716e0", method: .put).response(responseSerializer: dataResponseSerializer) { response in
switch response.result {
case .failure(let error):
print(error)
case .success(let value):
print(value)
}
}
}
And for a real and complete example of how async/await from Alamofire or any other async context look this code:
// This function get report from API and save to a local JSON to be readed by the app
func updateReport() {
Task {
guard let session = self.sessionRepository.getSession(WithUser: Defaults.lastLoggedUsername!) else { return }
guard let company = session.profile?.companies.first else { return }
self.apiManager.configure(WithToken: session.accessToken)
do {
let dateA = Date().dateAtStartOf(.year)
//let dateB = Date().dateAtEndOf(.month)
let dateB = Date() // Just now
let report = try await self.apiManager.report(CompanyId: company._id, DateA: dateA, DateB: dateB, ChartPeriodicity: .month)
self.currentReport = report
// Save data to disk to be read later
self.reportManager.saveReportToDisk(report: report!, withProfileId: session.profile!._id)
} catch {
print("Error getting report: \(error)")
}
}
}
// Get personal report from a given date range
func report(CompanyId companyId: String, DateA dateA: Date, DateB dateB: Date, ChartPeriodicity chartPeriodicity: ChartPeriodicity) async throws -> CDReport? {
try await withCheckedThrowingContinuation { continuation in
self.contappApi.request(.report(companyId: companyId, dateA: dateA, dateB: dateB, chartPeriodicity: chartPeriodicity)) { result in
switch result {
case let .success(response):
// Check status code
guard response.statusCode == 200 else {
continuation.resume(throwing: ContappNetworkError.unexpected(code: response.statusCode))
return
}
// Decode data
do {
//let report = try JSONDecoder().decode(CDReport.self, from: response.data)
let report = try CDReport(data: response.data)
continuation.resume(returning: report)
} catch {
continuation.resume(throwing: ContappNetworkError.cantDecodeDataFromNetwork)
}
case .failure(_):
continuation.resume(throwing: ContappNetworkError.networkError)
}
}
}
}
Alamofire already supports this, you just need to choose a form. Your biggest issue will be accepting a 200 with no data, as that's technically invalid since only 204 or 205 are supposed to be empty.
All Alamofire responses require some sort of payload type, but Alamofire provides an Empty type to fill this role for Decodable. So the simplest way is to use the
await AF.request(...)
.serializingDecodable(Empty.self, emptyResponseCodes: [200])
.response
Note, if you already have an Empty type or are importing Combine in the same file as this code, you may need to disambiguate by using Alamofire.Empty.
If Alamofire does not provide a method for your purpose, then you will have wrap the old Alamofire methods that uses closures as below:
func myRequest() async throws {
try await withUnsafeThrowingContinuation { continuation in
myAlamofireRequest {
continuation.resume()
}
}
}
I am currently writing an Alamofire HTTP request and am running into an issue where my view is not loading - likely because there is no data. The confusing part is that this was working yesterday. In the request I was able to do print(data) and the result was 506 bytes which, if my calculation is correct, is about the correct size given the JSON payload returned from the endpoint below.
#State var recipes = [Recipe]()
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
return
}
}
I can confirm that the endpoint that is being hit returns the following data...
[
{
"name":"Manhattan",
"image":"https://images.unsplash.com/photo-1536935338788-846bb9981813?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2486&q=80",
"spirit":"Bourbon",
"ice":"Crushed",
"glass":"Coupe",
"yield":"3.75",
"description":"This is a good drink. You should make it.",
"ingredients":[
{
"bottle":"High West Son Of Bourye",
"amount":"2.5"
},
{
"bottle":"Cocchi Vermouth Di Torino",
"amount":"0.75"
},
{
"bottle":"Simple Syrup",
"amount":"0.083"
}
]
}
]
I also have my Recipe and Ingredient model here which should be able to decode based on the above JSON.
struct Recipe: Decodable, Identifiable {
var id = UUID()
var name: String
var image: String
var spirit: String
var ice: String
var glass: String
var yield: String
var description: String
var ingredients: [Ingredient]
}
struct Ingredient: Decodable, Identifiable {
var id = UUID()
var bottle: String
var amount: String
}
Is anybody able to spot an issue? I was trying to put a debugging print in the DispatchQueue but it is not printing which, to me, sounds like an error. However I am new to Swift/XCode/iOS and am not sure the best debugging practices for this.
If you can't debug yourself, NEVER USE try?. With more experience, I'd say that we tend to not use try?, but sometimes we do. But when we write try?, we are able to find an possible issue, ie debug if needed.
Let's do a proper try then, with a do/catch:
do {
let response = try JSONDecoder().decode([Recipe].self, from: data
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
}
And read the output.
Going further?
Don't believe what's the API is supposed to return.
I've seen plenty and plenty of questions where the returned values was an error message, a XML Error message, a JSON Error message, an HTML Error message, and a JSON value missing, or of bad type, etc. And that, your JSONDecoder wasn't expecting it...
Reasons could be various, from bad/missing parameters, bad/missing APIKey, server down, bad/missing header, etc.
But, then, print the returned value.
print(String(data: data, encoding: .utf8) ?? "No data found")
So print it directly when you get it, or at least in the catch:
} catch {
print("Oops, there was en error while decoding: \(error)") // and not error.localizedDescription as it's more for users than developpers, so you'll skip all the useful informations
print("While getting response stringified: \(String(data: data, encoding: .utf8) ?? "No data found")")
}
If you don't understand the error message output, it's okay, there is no shame about it. But your first job is to get that error message. You can share it on SO if you don't understand it, you might get help with that. But currently, we can't guess what's wrong with your code.
It's a good idea to drop some clues in your code when looking for a failure.
If it were me I'd do something like this:
AF.request("http://localhost:3000/recipes").responseJSON { response in
guard let data = response.data else {
print("Error trying to receive data in ", #file, #function)
return
}
do {
let response = try JSONDecoder().decode([Recipe].self, from: data) {
DispatchQueue.main.async {
self.recipes = response
}
} catch {
print("Error failed to decode json data with error: \(error) in \(#file)", #function)
}
}
I have an API client that uses generic API response that conforms to Codable Protocol and uses JSONDecoder to decode the response as shown below, how do I handle having a response which doesn't return JSON ( status code 201 created)?
dataRequest.validate().responseJSON { response in
if let error = response.error {
completion(.failure(error.localizedDescription))
} else if let data = response.data {
do {
let apiResponse = try JSONDecoder().decode(T.Response.self, from: data)
completion(.success(apiResponse))
} catch {
completion(.failure(error.localizedDescription))
}
} else {
completion(.failure("Something went wrong, please try again later."))
}
}
It returns this error:
the response could not be serialized input data was nil or zero-length
In this case you can look at the statusCode property of the response (assuming that it is a HTTPURLResponse) and make your determination about whether or not there will be a body to parse. I would put it immediately after the error check.
I try to decode the response returned from a catch, here is what I tried :
try{
...
} catch (err) {
//JsonCodec codec = new JsonCodec(); // doesn't work
//var decoded = codec.decode(err);
...
}
Error: type '_Exception' is not a subtype of type 'String'
print(err) :
Exception: {
"error": {
"code": "invalid_expiry_year"
}
}
I would like to get the value of "code", I tried many things but it doesn't work,
Any idea?
print(err.code);
then I get :
You could get the message property and jsonDecode it.
try {
} catch (e) {
var message = e.message; // not guaranteed to work if the caught exception isn't an _Exception instance
var decoded = jsonDecode(message);
print(decoded['error']['code'])'
}
All of this strikes as a misuse of Exception. Note that you generally don't want to throw Exception(message);. Putting a json encoded message in there is not the best way to communicate details about the exception. Instead, write a custom implementation of Exception and catch the specific type.
class InvalidDataException implements Exception {
final String code;
InvalidDataException(this.code);
}
try {
} on InvalidDataException catch(e) {
print(e.code); // guaranteed to work, we know the type of the exception
}
See the docs for Exception:
Creating instances of Exception directly with new Exception("message") is discouraged, and only included as a temporary measure during development, until the actual exceptions used by a library are done.
See also https://www.dartlang.org/guides/language/effective-dart/usage#avoid-catches-without-on-clauses
I finally figured out a solution :
catch (response) {
var err=response.toString().replaceAll('Exception: ', '');
final json=JSON.jsonDecode(err);
print(json['error']['code']);
}
I'm using swiftHTTP for requesting to my server and when my internet connection is slow, it goes to response part! I've set the example code below:
HTTP.GET("myURL") { response in
let myResponse = response.data // it comes here after the timeout
if response.statusCode == 200 {
//some code
} else {
do {
let jsonError = try JSON(data: myResponse) // in this line it goes to catch because there is no data in myresponse
} catch{
//alert for not having connection
}
}
Why does it call the response function if there's no response?
My server also says, that no request was sent.
It doesn't "go to response", it tries to make the HTTP request as expected and regardless of success or error it's completion handler is called.
The response object that is returned is an object that contains all of the information you need to determine what happened with the request.
So it will contain a timeout status code (HTTP 408), possibly an error message. If it did not respond at all, your app would not be able to handle these cases.
Say for example your user taps on their profile icon in the app and this sends a request to get the users profile, but it timed out. Do you want the user sat waiting, looking at a blank profile screen? It's much better to catch the error and handle it gracefully. In this case you could present a message to the user telling them that something went wrong and close the empty profile screen
Your response handler will also be called from swiftHTTP, if there's no or a very bad connection.To solve this problem, either check if there is an internet connection or check if the data is nil:
HTTP.GET("myURL") { response in
let myResponse = response.data // it comes here after the timeout
if response.statusCode == 200 || response.data == nil {
//some code
} else {
do {
let jsonError = try JSON(data: myResponse) // in this line it goes to catch because there is no data in myresponse
} catch{
//alert for not having connection
}
}
The important part here is the check if response.data == nil.