mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Some checks failed
The request data is logged anyway, so it's nicer if the error string only has the important part.
186 lines
6.7 KiB
Go
186 lines
6.7 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"
|
|
|
|
"go.mau.fi/util/exhttp"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
// 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 (
|
|
// Generic error for when the server encounters an error and it does not have a more specific error code.
|
|
// Note that `errors.Is` will check the error message rather than code for M_UNKNOWNs.
|
|
MUnknown = RespError{ErrCode: "M_UNKNOWN", StatusCode: http.StatusInternalServerError}
|
|
// Forbidden access, e.g. joining a room without permission, failed login.
|
|
MForbidden = RespError{ErrCode: "M_FORBIDDEN", StatusCode: http.StatusForbidden}
|
|
// Unrecognized request, e.g. the endpoint does not exist or is not implemented.
|
|
MUnrecognized = RespError{ErrCode: "M_UNRECOGNIZED", StatusCode: http.StatusNotFound}
|
|
// The access token specified was not recognised.
|
|
MUnknownToken = RespError{ErrCode: "M_UNKNOWN_TOKEN", StatusCode: http.StatusUnauthorized}
|
|
// No access token was specified for the request.
|
|
MMissingToken = RespError{ErrCode: "M_MISSING_TOKEN", StatusCode: http.StatusUnauthorized}
|
|
// 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", StatusCode: http.StatusBadRequest}
|
|
// Request did not contain valid JSON.
|
|
MNotJSON = RespError{ErrCode: "M_NOT_JSON", StatusCode: http.StatusBadRequest}
|
|
// No resource was found for this request.
|
|
MNotFound = RespError{ErrCode: "M_NOT_FOUND", StatusCode: http.StatusNotFound}
|
|
// Too many requests have been sent in a short period of time. Wait a while then try again.
|
|
MLimitExceeded = RespError{ErrCode: "M_LIMIT_EXCEEDED", StatusCode: http.StatusTooManyRequests}
|
|
// 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", StatusCode: http.StatusBadRequest}
|
|
// Encountered when trying to register a user ID which is not valid.
|
|
MInvalidUsername = RespError{ErrCode: "M_INVALID_USERNAME", StatusCode: http.StatusBadRequest}
|
|
// Sent when the room alias given to the createRoom API is already in use.
|
|
MRoomInUse = RespError{ErrCode: "M_ROOM_IN_USE", StatusCode: http.StatusBadRequest}
|
|
// 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", StatusCode: http.StatusRequestEntityTooLarge}
|
|
// 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", StatusCode: http.StatusBadRequest}
|
|
// 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"}
|
|
// The client specified a parameter that has the wrong value.
|
|
MInvalidParam = RespError{ErrCode: "M_INVALID_PARAM", StatusCode: http.StatusBadRequest}
|
|
|
|
MURLNotSet = RespError{ErrCode: "M_URL_NOT_SET"}
|
|
MBadStatus = RespError{ErrCode: "M_BAD_STATUS"}
|
|
MConnectionTimeout = RespError{ErrCode: "M_CONNECTION_TIMEOUT"}
|
|
MConnectionFailed = RespError{ErrCode: "M_CONNECTION_FAILED"}
|
|
)
|
|
|
|
// 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("%s (HTTP %d): %s", e.RespError.ErrCode, e.Response.StatusCode, e.RespError.Err)
|
|
} else {
|
|
msg := fmt.Sprintf("HTTP %d", e.Response.StatusCode)
|
|
if len(e.ResponseBody) > 0 {
|
|
msg = fmt.Sprintf("%s: %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]any
|
|
|
|
StatusCode int
|
|
}
|
|
|
|
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) {
|
|
data := maps.Clone(e.ExtraData)
|
|
if data == nil {
|
|
data = make(map[string]any)
|
|
}
|
|
data["errcode"] = e.ErrCode
|
|
data["error"] = e.Err
|
|
return json.Marshal(data)
|
|
}
|
|
|
|
func (e RespError) Write(w http.ResponseWriter) {
|
|
if w == nil {
|
|
return
|
|
}
|
|
statusCode := e.StatusCode
|
|
if statusCode == 0 {
|
|
statusCode = http.StatusInternalServerError
|
|
}
|
|
exhttp.WriteJSONResponse(w, statusCode, &e)
|
|
}
|
|
|
|
func (e RespError) WithMessage(msg string, args ...any) RespError {
|
|
if len(args) > 0 {
|
|
msg = fmt.Sprintf(msg, args...)
|
|
}
|
|
e.Err = msg
|
|
return e
|
|
}
|
|
|
|
func (e RespError) WithStatus(status int) RespError {
|
|
e.StatusCode = status
|
|
return e
|
|
}
|
|
|
|
// 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
|
|
}
|