145 lines
5.0 KiB
Go
145 lines
5.0 KiB
Go
|
// Copyright (c) 2020 Tulir Asokan
|
||
|
//
|
||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
package mautrix
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
)
|
||
|
|
||
|
// Common error codes from https://matrix.org/docs/spec/client_server/latest#api-standards
|
||
|
//
|
||
|
// Can be used with errors.Is() to check the response code without casting the error:
|
||
|
// err := client.Sync()
|
||
|
// if errors.Is(err, MUnknownToken) {
|
||
|
// // logout
|
||
|
// }
|
||
|
var (
|
||
|
// Forbidden access, e.g. joining a room without permission, failed login.
|
||
|
MForbidden = RespError{ErrCode: "M_FORBIDDEN"}
|
||
|
// The access token specified was not recognised.
|
||
|
MUnknownToken = RespError{ErrCode: "M_UNKNOWN_TOKEN"}
|
||
|
// No access token was specified for the request.
|
||
|
MMissingToken = RespError{ErrCode: "M_MISSING_TOKEN"}
|
||
|
// Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys.
|
||
|
MBadJSON = RespError{ErrCode: "M_BAD_JSON"}
|
||
|
// Request did not contain valid JSON.
|
||
|
MNotJSON = RespError{ErrCode: "M_NOT_JSON"}
|
||
|
// No resource was found for this request.
|
||
|
MNotFound = RespError{ErrCode: "M_NOT_FOUND"}
|
||
|
// Too many requests have been sent in a short period of time. Wait a while then try again.
|
||
|
MLimitExceeded = RespError{ErrCode: "M_LIMIT_EXCEEDED"}
|
||
|
// The user ID associated with the request has been deactivated.
|
||
|
// Typically for endpoints that prove authentication, such as /login.
|
||
|
MUserDeactivated = RespError{ErrCode: "M_USER_DEACTIVATED"}
|
||
|
// Encountered when trying to register a user ID which has been taken.
|
||
|
MUserInUse = RespError{ErrCode: "M_USER_IN_USE"}
|
||
|
// Encountered when trying to register a user ID which is not valid.
|
||
|
MInvalidUsername = RespError{ErrCode: "M_INVALID_USERNAME"}
|
||
|
// Sent when the room alias given to the createRoom API is already in use.
|
||
|
MRoomInUse = RespError{ErrCode: "M_ROOM_IN_USE"}
|
||
|
// The state change requested cannot be performed, such as attempting to unban a user who is not banned.
|
||
|
MBadState = RespError{ErrCode: "M_BAD_STATE"}
|
||
|
// The request or entity was too large.
|
||
|
MTooLarge = RespError{ErrCode: "M_TOO_LARGE"}
|
||
|
// The resource being requested is reserved by an application service, or the application service making the request has not created the resource.
|
||
|
MExclusive = RespError{ErrCode: "M_EXCLUSIVE"}
|
||
|
// The client's request to create a room used a room version that the server does not support.
|
||
|
MUnsupportedRoomVersion = RespError{ErrCode: "M_UNSUPPORTED_ROOM_VERSION"}
|
||
|
// The client attempted to join a room that has a version the server does not support.
|
||
|
// Inspect the room_version property of the error response for the room's version.
|
||
|
MIncompatibleRoomVersion = RespError{ErrCode: "M_INCOMPATIBLE_ROOM_VERSION"}
|
||
|
)
|
||
|
|
||
|
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
|
||
|
type HTTPError struct {
|
||
|
Request *http.Request
|
||
|
Response *http.Response
|
||
|
ResponseBody string
|
||
|
|
||
|
WrappedError error
|
||
|
RespError *RespError
|
||
|
Message string
|
||
|
}
|
||
|
|
||
|
func (e HTTPError) Is(err error) bool {
|
||
|
return (e.RespError != nil && errors.Is(e.RespError, err)) || (e.WrappedError != nil && errors.Is(e.WrappedError, err))
|
||
|
}
|
||
|
|
||
|
func (e HTTPError) IsStatus(code int) bool {
|
||
|
return e.Response != nil && e.Response.StatusCode == code
|
||
|
}
|
||
|
|
||
|
func (e HTTPError) Error() string {
|
||
|
if e.WrappedError != nil {
|
||
|
return fmt.Sprintf("%s: %v", e.Message, e.WrappedError)
|
||
|
} else if e.RespError != nil {
|
||
|
return fmt.Sprintf("failed to %s %s: %s (HTTP %d): %s", e.Request.Method, e.Request.URL.Path,
|
||
|
e.RespError.ErrCode, e.Response.StatusCode, e.RespError.Err)
|
||
|
} else {
|
||
|
msg := fmt.Sprintf("failed to %s %s: %s", e.Request.Method, e.Request.URL.Path, e.Response.Status)
|
||
|
if len(e.ResponseBody) > 0 {
|
||
|
msg = fmt.Sprintf("%s\n%s", msg, e.ResponseBody)
|
||
|
}
|
||
|
return msg
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (e HTTPError) Unwrap() error {
|
||
|
if e.WrappedError != nil {
|
||
|
return e.WrappedError
|
||
|
} else if e.RespError != nil {
|
||
|
return *e.RespError
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
|
||
|
// See https://spec.matrix.org/v1.2/client-server-api/#api-standards
|
||
|
type RespError struct {
|
||
|
ErrCode string
|
||
|
Err string
|
||
|
ExtraData map[string]interface{}
|
||
|
}
|
||
|
|
||
|
func (e *RespError) UnmarshalJSON(data []byte) error {
|
||
|
err := json.Unmarshal(data, &e.ExtraData)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
e.ErrCode, _ = e.ExtraData["errcode"].(string)
|
||
|
e.Err, _ = e.ExtraData["error"].(string)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (e *RespError) MarshalJSON() ([]byte, error) {
|
||
|
if e.ExtraData == nil {
|
||
|
e.ExtraData = make(map[string]interface{})
|
||
|
}
|
||
|
e.ExtraData["errcode"] = e.ErrCode
|
||
|
e.ExtraData["error"] = e.Err
|
||
|
return json.Marshal(&e.ExtraData)
|
||
|
}
|
||
|
|
||
|
// Error returns the errcode and error message.
|
||
|
func (e RespError) Error() string {
|
||
|
return e.ErrCode + ": " + e.Err
|
||
|
}
|
||
|
|
||
|
func (e RespError) Is(err error) bool {
|
||
|
e2, ok := err.(RespError)
|
||
|
if !ok {
|
||
|
return false
|
||
|
}
|
||
|
if e.ErrCode == "M_UNKNOWN" && e2.ErrCode == "M_UNKNOWN" {
|
||
|
return e.Err == e2.Err
|
||
|
}
|
||
|
return e2.ErrCode == e.ErrCode
|
||
|
}
|