errors: refactor

pull/452/head
Demian 2 years ago
parent 10b70148f7
commit 9805b1f622

@ -70,5 +70,5 @@ func TestRaw(t *testing.T) {
assert.EqualError(t, err, "telebot: "+io.ErrUnexpectedEOF.Error())
_, err = b.Raw("testUnknownError", nil)
assert.EqualError(t, err, "telegram unknown: unknown error (400)")
assert.EqualError(t, err, "telegram: unknown error (400)")
}

@ -5,26 +5,32 @@ import (
"strings"
)
type APIError struct {
Code int
Description string
Message string
Parameters map[string]interface{}
}
type (
Error struct {
Code int
Description string
Message string
}
type FloodError struct {
*APIError
RetryAfter int
}
FloodError struct {
err *Error
RetryAfter int
}
GroupError struct {
err *Error
MigratedTo int64
}
)
// ʔ returns description of error.
// A tiny shortcut to make code clearer.
func (err *APIError) ʔ() string {
func (err *Error) ʔ() string {
return err.Description
}
// Error implements error interface.
func (err *APIError) Error() string {
func (err *Error) Error() string {
msg := err.Message
if msg == "" {
split := strings.Split(err.Description, ": ")
@ -37,10 +43,20 @@ func (err *APIError) Error() string {
return fmt.Sprintf("telegram: %s (%d)", msg, err.Code)
}
// NewAPIError returns new APIError instance with given description.
// Error implements error interface.
func (err FloodError) Error() string {
return err.err.Error()
}
// Error implements error interface.
func (err GroupError) Error() string {
return err.err.Error()
}
// NewError returns new Error instance with given description.
// First element of msgs is Description. The second is optional Message.
func NewAPIError(code int, msgs ...string) *APIError {
err := &APIError{Code: code}
func NewError(code int, msgs ...string) *Error {
err := &Error{Code: code}
if len(msgs) >= 1 {
err.Description = msgs[0]
}
@ -50,57 +66,63 @@ func NewAPIError(code int, msgs ...string) *APIError {
return err
}
// General errors
var (
// General errors
ErrUnauthorized = NewAPIError(401, "Unauthorized")
ErrNotStartedByUser = NewAPIError(403, "Forbidden: bot can't initiate conversation with a user")
ErrBlockedByUser = NewAPIError(401, "Forbidden: bot was blocked by the user")
ErrUserIsDeactivated = NewAPIError(401, "Forbidden: user is deactivated")
ErrNotFound = NewAPIError(404, "Not Found")
ErrInternal = NewAPIError(500, "Internal Server Error")
ErrUnauthorized = NewError(401, "Unauthorized")
ErrNotStartedByUser = NewError(403, "Forbidden: bot can't initiate conversation with a user")
ErrBlockedByUser = NewError(401, "Forbidden: bot was blocked by the user")
ErrUserIsDeactivated = NewError(401, "Forbidden: user is deactivated")
ErrNotFound = NewError(404, "Not Found")
ErrInternal = NewError(500, "Internal Server Error")
)
// Bad request errors
ErrTooLarge = NewAPIError(400, "Request Entity Too Large")
ErrMessageTooLong = NewAPIError(400, "Bad Request: message is too long")
ErrToForwardNotFound = NewAPIError(400, "Bad Request: message to forward not found")
ErrToReplyNotFound = NewAPIError(400, "Bad Request: reply message not found")
ErrToDeleteNotFound = NewAPIError(400, "Bad Request: message to delete not found")
ErrEmptyMessage = NewAPIError(400, "Bad Request: message must be non-empty")
ErrEmptyText = NewAPIError(400, "Bad Request: text is empty")
ErrEmptyChatID = NewAPIError(400, "Bad Request: chat_id is empty")
ErrChatNotFound = NewAPIError(400, "Bad Request: chat not found")
ErrMessageNotModified = NewAPIError(400, "Bad Request: message is not modified")
ErrSameMessageContent = NewAPIError(400, "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")
ErrCantEditMessage = NewAPIError(400, "Bad Request: message can't be edited")
ErrButtonDataInvalid = NewAPIError(400, "Bad Request: BUTTON_DATA_INVALID")
ErrWrongTypeOfContent = NewAPIError(400, "Bad Request: wrong type of the web page content")
ErrBadURLContent = NewAPIError(400, "Bad Request: failed to get HTTP URL content")
ErrWrongFileID = NewAPIError(400, "Bad Request: wrong file identifier/HTTP URL specified")
ErrWrongFileIDSymbol = NewAPIError(400, "Bad Request: wrong remote file id specified: can't unserialize it. Wrong last symbol")
ErrWrongFileIDLength = NewAPIError(400, "Bad Request: wrong remote file id specified: Wrong string length")
ErrWrongFileIDCharacter = NewAPIError(400, "Bad Request: wrong remote file id specified: Wrong character in the string")
ErrWrongFileIDPadding = NewAPIError(400, "Bad Request: wrong remote file id specified: Wrong padding in the string")
ErrFailedImageProcess = NewAPIError(400, "Bad Request: IMAGE_PROCESS_FAILED", "Image process failed")
ErrInvalidStickerSet = NewAPIError(400, "Bad Request: STICKERSET_INVALID", "Stickerset is invalid")
ErrBadPollOptions = NewAPIError(400, "Bad Request: expected an Array of String as options")
ErrGroupMigrated = NewAPIError(400, "Bad Request: group chat was upgraded to a supergroup chat")
// Bad request errors
var (
ErrTooLarge = NewError(400, "Request Entity Too Large")
ErrMessageTooLong = NewError(400, "Bad Request: message is too long")
ErrToForwardNotFound = NewError(400, "Bad Request: message to forward not found")
ErrToReplyNotFound = NewError(400, "Bad Request: reply message not found")
ErrToDeleteNotFound = NewError(400, "Bad Request: message to delete not found")
ErrEmptyMessage = NewError(400, "Bad Request: message must be non-empty")
ErrEmptyText = NewError(400, "Bad Request: text is empty")
ErrEmptyChatID = NewError(400, "Bad Request: chat_id is empty")
ErrChatNotFound = NewError(400, "Bad Request: chat not found")
ErrMessageNotModified = NewError(400, "Bad Request: message is not modified")
ErrSameMessageContent = NewError(400, "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message")
ErrCantEditMessage = NewError(400, "Bad Request: message can't be edited")
ErrButtonDataInvalid = NewError(400, "Bad Request: BUTTON_DATA_INVALID")
ErrWrongTypeOfContent = NewError(400, "Bad Request: wrong type of the web page content")
ErrBadURLContent = NewError(400, "Bad Request: failed to get HTTP URL content")
ErrWrongFileID = NewError(400, "Bad Request: wrong file identifier/HTTP URL specified")
ErrWrongFileIDSymbol = NewError(400, "Bad Request: wrong remote file id specified: can't unserialize it. Wrong last symbol")
ErrWrongFileIDLength = NewError(400, "Bad Request: wrong remote file id specified: Wrong string length")
ErrWrongFileIDCharacter = NewError(400, "Bad Request: wrong remote file id specified: Wrong character in the string")
ErrWrongFileIDPadding = NewError(400, "Bad Request: wrong remote file id specified: Wrong padding in the string")
ErrFailedImageProcess = NewError(400, "Bad Request: IMAGE_PROCESS_FAILED", "Image process failed")
ErrInvalidStickerSet = NewError(400, "Bad Request: STICKERSET_INVALID", "Stickerset is invalid")
ErrBadPollOptions = NewError(400, "Bad Request: expected an Array of String as options")
ErrGroupMigrated = NewError(400, "Bad Request: group chat was upgraded to a supergroup chat")
)
// No rights errors
ErrNoRightsToRestrict = NewAPIError(400, "Bad Request: not enough rights to restrict/unrestrict chat member")
ErrNoRightsToSend = NewAPIError(400, "Bad Request: have no rights to send a message")
ErrNoRightsToSendPhoto = NewAPIError(400, "Bad Request: not enough rights to send photos to the chat")
ErrNoRightsToSendStickers = NewAPIError(400, "Bad Request: not enough rights to send stickers to the chat")
ErrNoRightsToSendGifs = NewAPIError(400, "Bad Request: CHAT_SEND_GIFS_FORBIDDEN", "sending GIFS is not allowed in this chat")
ErrNoRightsToDelete = NewAPIError(400, "Bad Request: message can't be deleted")
ErrKickingChatOwner = NewAPIError(400, "Bad Request: can't remove chat owner")
// No rights errors
var (
ErrNoRightsToRestrict = NewError(400, "Bad Request: not enough rights to restrict/unrestrict chat member")
ErrNoRightsToSend = NewError(400, "Bad Request: have no rights to send a message")
ErrNoRightsToSendPhoto = NewError(400, "Bad Request: not enough rights to send photos to the chat")
ErrNoRightsToSendStickers = NewError(400, "Bad Request: not enough rights to send stickers to the chat")
ErrNoRightsToSendGifs = NewError(400, "Bad Request: CHAT_SEND_GIFS_FORBIDDEN", "sending GIFS is not allowed in this chat")
ErrNoRightsToDelete = NewError(400, "Bad Request: message can't be deleted")
ErrKickingChatOwner = NewError(400, "Bad Request: can't remove chat owner")
)
// Super/groups errors
ErrBotKickedFromGroup = NewAPIError(403, "Forbidden: bot was kicked from the group chat")
ErrBotKickedFromSuperGroup = NewAPIError(403, "Forbidden: bot was kicked from the supergroup chat")
// Super/groups errors
var (
ErrBotKickedFromGroup = NewError(403, "Forbidden: bot was kicked from the group chat")
ErrBotKickedFromSuperGroup = NewError(403, "Forbidden: bot was kicked from the supergroup chat")
)
// ErrByDescription returns APIError instance by given description.
func ErrByDescription(s string) error {
// Err returns Error instance by given description.
func Err(s string) error {
switch s {
case ErrUnauthorized.ʔ():
return ErrUnauthorized

@ -57,52 +57,49 @@ func wrapError(err error) error {
// In other cases it extracts API error. If error is not presented
// in errors.go, it will be prefixed with `unknown` keyword.
func extractOk(data []byte) error {
// Parse the error message as JSON
var tgramApiError struct {
var e struct {
Ok bool `json:"ok"`
ErrorCode int `json:"error_code"`
Code int `json:"error_code"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
}
jdecoder := json.NewDecoder(bytes.NewReader(data))
jdecoder.UseNumber()
err := jdecoder.Decode(&tgramApiError)
if err != nil {
//return errors.Wrap(err, "can't parse JSON reply, the Telegram server is mibehaving")
// FIXME / TODO: in this case the error might be at HTTP level, or the content is not JSON (eg. image?)
return nil
if json.NewDecoder(bytes.NewReader(data)).Decode(&e) != nil {
return nil // FIXME
}
if tgramApiError.Ok {
// No error
if e.Ok {
return nil
}
err = ErrByDescription(tgramApiError.Description)
if err != nil {
apierr, _ := err.(*APIError)
// Formally this is wrong, as the error is not created on the fly
// However, given the current way of handling errors, this a working
// workaround which doesn't break the API
apierr.Parameters = tgramApiError.Parameters
return apierr
err := Err(e.Description)
switch err {
case nil:
case ErrGroupMigrated:
migratedTo, ok := e.Parameters["migrate_to_chat_id"]
if !ok {
return NewError(e.Code, e.Description)
}
return GroupError{
err: err.(*Error),
MigratedTo: int64(migratedTo.(float64)),
}
default:
return err
}
switch tgramApiError.ErrorCode {
switch e.Code {
case http.StatusTooManyRequests:
retryAfter, ok := tgramApiError.Parameters["retry_after"]
retryAfter, ok := e.Parameters["retry_after"]
if !ok {
return NewAPIError(429, tgramApiError.Description)
return NewError(e.Code, e.Description)
}
retryAfterInt, _ := strconv.Atoi(fmt.Sprint(retryAfter))
err = FloodError{
APIError: NewAPIError(429, tgramApiError.Description),
RetryAfter: retryAfterInt,
err: NewError(e.Code, e.Description),
RetryAfter: int(retryAfter.(float64)),
}
default:
err = fmt.Errorf("telegram unknown: %s (%d)", tgramApiError.Description, tgramApiError.ErrorCode)
err = fmt.Errorf("telegram: %s (%d)", e.Description, e.Code)
}
return err

@ -8,20 +8,37 @@ import (
)
func TestExtractOk(t *testing.T) {
data := []byte(`{"ok":true,"result":{"foo":"bar"}}`)
data := []byte(`{"ok": true, "result": {}}`)
require.NoError(t, extractOk(data))
data = []byte(`{"ok":false,"error_code":400,"description":"Bad Request: reply message not found"}`)
data = []byte(`{
"ok": false,
"error_code": 400,
"description": "Bad Request: reply message not found"
}`)
assert.EqualError(t, extractOk(data), ErrToReplyNotFound.Error())
data = []byte(`{"ok":false,"error_code":429,"description":"Too Many Requests: retry after 8","parameters":{"retry_after":8}}`)
data = []byte(`{
"ok": false,
"error_code": 429,
"description": "Too Many Requests: retry after 8",
"parameters": {"retry_after": 8}
}`)
assert.Equal(t, FloodError{
APIError: NewAPIError(429, "Too Many Requests: retry after 8"),
err: NewError(429, "Too Many Requests: retry after 8"),
RetryAfter: 8,
}, extractOk(data))
data = []byte(`{"ok":false,"error_code":400,"description":"Bad Request: group chat was upgraded to a supergroup chat","parameters":{"migrate_to_chat_id": -1234}}`)
assert.EqualError(t, extractOk(data), ErrGroupMigrated.Error())
data = []byte(`{
"ok": false,
"error_code": 400,
"description": "Bad Request: group chat was upgraded to a supergroup chat",
"parameters": {"migrate_to_chat_id": -100123456789}
}`)
assert.Equal(t, GroupError{
err: ErrGroupMigrated,
MigratedTo: -100123456789,
}, extractOk(data))
}
func TestExtractMessage(t *testing.T) {

Loading…
Cancel
Save