Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper handling of errors returned by telegram API #1446

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.telegram.telegrambots.client.okhttp;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;

@Slf4j
abstract class AbstractOkHttpFutureCallback<T> extends CompletableFuture<T> implements Callback {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException exception) {
completeExceptionally(exception);
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (!response.isSuccessful()) {
var messageBuilder = new StringBuilder();
messageBuilder.append("code: ").append(response.code());
messageBuilder.append(", message: ").append(response.message());

if(response.body() != null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure you can do it? shouldn't response.body() be called only once? multiple calls will cause an exception

Copy link

@Name-not-available Name-not-available Nov 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and all future or past calls too

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just returns a value of the body field
This if just a check that body exists and accessing it will not give us an NPE

But the body is a ResponseBody instance and is consumable and should be consumed once

And here I call .string() which consumes body and closes it, so this piece does not require try-with-resources notation

try {
messageBuilder.append(", response body: ").append(response.body().string());
} catch (IOException e) {
log.error("Error reading response body", e);
}
}

completeExceptionally(new TelegramApiException("Telegram API returned error: " + messageBuilder));
return;
}

try(ResponseBody body = response.body()) {
if (body == null) {
completeExceptionally(new TelegramApiException("Telegram api returned empty response"));
} else {
complete(getResponseValue(body));
}
} catch (Exception e) {
completeExceptionally(e);
}
}

protected abstract T getResponseValue(ResponseBody body) throws IOException, TelegramApiRequestException;
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,21 @@
package org.telegram.telegrambots.client.okhttp;

import lombok.NonNull;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.telegram.telegrambots.meta.api.methods.botapimethods.PartialBotApiMethod;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;

import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;

class OkHttpFutureCallback<T extends Serializable, Method extends PartialBotApiMethod<T>> extends CompletableFuture<T> implements Callback {
class OkHttpFutureCallback<T extends Serializable, Method extends PartialBotApiMethod<T>> extends AbstractOkHttpFutureCallback<T> {
private final Method method;

OkHttpFutureCallback(Method method) {
this.method = method;
}

@Override
public void onFailure(@NonNull Call call, @NonNull IOException exception) {
completeExceptionally(exception);
protected T getResponseValue(ResponseBody body) throws IOException, TelegramApiRequestException {
return method.deserializeResponse(body.string());
}

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
try(ResponseBody body = response.body()) {
if (body == null) {
completeExceptionally(new TelegramApiException("Telegram api returned empty response"));
} else {
try {
complete(method.deserializeResponse(body.string()));
} catch (TelegramApiRequestException e) {
completeExceptionally(e);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,16 @@
package org.telegram.telegrambots.client.okhttp;

import lombok.NonNull;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.io.IOUtils;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;

class OkHttpFutureDownloadCallback extends CompletableFuture<InputStream> implements Callback {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException exception) {
completeExceptionally(exception);
}
class OkHttpFutureDownloadCallback extends AbstractOkHttpFutureCallback<InputStream> {

@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
try(ResponseBody body = response.body()) {
if (body == null) {
completeExceptionally(new TelegramApiException("Telegram api returned empty response"));
} else {
complete(new ByteArrayInputStream(IOUtils.toByteArray(body.byteStream())));
}
} catch (Exception e) {
completeExceptionally(e);
}
protected InputStream getResponseValue(ResponseBody body) throws IOException {
return new ByteArrayInputStream(IOUtils.toByteArray(body.byteStream()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ private List<Update> getUpdatesFromTelegram() throws TelegramApiRequestException
}
}
} else {
throw new TelegramApiErrorResponseException(response.code(), response.message());

throw new TelegramApiErrorResponseException(extractApiErrorDetails(response));
}
}
} catch (Exception e) {
Expand Down Expand Up @@ -177,14 +178,30 @@ private void executeDeleteWebhook() throws TelegramApiRequestException, Telegram
}
}
} else {
throw new TelegramApiErrorResponseException(response.code(), response.message());
throw new TelegramApiErrorResponseException(extractApiErrorDetails(response));
}
}
} catch (IOException e) {
throw new TelegramApiErrorResponseException("Unable to execute " + deleteWebhook.getMethod() + " method", e);
}
}

private String extractApiErrorDetails(Response response) {
var messageBuilder = new StringBuilder();
messageBuilder.append("code: ").append(response.code());
messageBuilder.append(", message: ").append(response.message());

if(response.body() != null) {
try {
messageBuilder.append(", response body: ").append(response.body().string());
} catch (IOException e) {
log.error("Error reading response body", e);
}
}

return messageBuilder.toString();
}

@NonNull
private Request createRequest(TelegramUrl telegramUrl, BotApiMethod<?> apiMethod) throws JsonProcessingException {
return new Request.Builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

public class TelegramApiErrorResponseException extends TelegramApiException {
private final int code;

public TelegramApiErrorResponseException(String message) {
this(message, null);
Expand All @@ -15,16 +14,5 @@ public TelegramApiErrorResponseException(Throwable cause) {

public TelegramApiErrorResponseException(String message, Throwable cause) {
super(message, cause);
this.code = -1;
}

public TelegramApiErrorResponseException(int code, String message) {
super(message);
this.code = code;
}

@Override
public String toString() {
return code + ": " + super.toString();
}
}