Files

376 lines
9.3 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package psrpc
import (
"errors"
"fmt"
"io"
"net/http"
"github.com/twitchtv/twirp"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
var (
ErrRequestCanceled = NewErrorf(Canceled, "request canceled")
ErrRequestTimedOut = NewErrorf(DeadlineExceeded, "request timed out")
ErrNoResponse = NewErrorf(Unavailable, "no response from servers")
ErrUnimplemented = NewErrorf(Unimplemented, "method is not implemented")
ErrStreamEOF = NewError(Unavailable, io.EOF)
ErrClientClosed = NewErrorf(Canceled, "client is closed")
ErrServerClosed = NewErrorf(Canceled, "server is closed")
ErrStreamClosed = NewErrorf(Canceled, "stream closed")
ErrSlowConsumer = NewErrorf(Unavailable, "stream message discarded by slow consumer")
)
type Error interface {
error
Code() ErrorCode
Details() []any
DetailsProto() []*anypb.Any
// convenience methods
ToHttp() int
GRPCStatus() *status.Status
}
type ErrorCode string
func (e ErrorCode) Error() string {
return string(e)
}
func (e ErrorCode) ToHTTP() int {
switch e {
case OK:
return http.StatusOK
case Unknown, MalformedResponse, Internal, DataLoss:
return http.StatusInternalServerError
case InvalidArgument, MalformedRequest:
return http.StatusBadRequest
case NotFound:
return http.StatusNotFound
case NotAcceptable:
return http.StatusNotAcceptable
case AlreadyExists, Aborted:
return http.StatusConflict
case PermissionDenied:
return http.StatusForbidden
case ResourceExhausted:
return http.StatusTooManyRequests
case FailedPrecondition:
return http.StatusPreconditionFailed
case OutOfRange:
return http.StatusRequestedRangeNotSatisfiable
case Unimplemented:
return http.StatusNotImplemented
case Canceled, DeadlineExceeded, Unavailable:
return http.StatusServiceUnavailable
case Unauthenticated:
return http.StatusUnauthorized
case UnprocessableEntity:
return http.StatusUnprocessableEntity
case UpstreamServerError:
return http.StatusBadGateway
case UpstreamClientError:
return http.StatusFailedDependency
default:
return http.StatusInternalServerError
}
}
func GetErrorCode(err error) (ErrorCode, bool) {
var e Error
if errors.As(err, &e) {
return e.Code(), true
}
if st, ok := status.FromError(err); ok && st != nil {
return ErrorCodeFromGRPC(st.Code()), true
}
return Unknown, false
}
func ErrorCodeFromGRPC(code codes.Code) ErrorCode {
switch code {
case codes.OK:
return OK
case codes.Canceled:
return Canceled
case codes.Unknown:
return Unknown
case codes.InvalidArgument:
return InvalidArgument
case codes.DeadlineExceeded:
return DeadlineExceeded
case codes.NotFound:
return NotFound
case codes.AlreadyExists:
return AlreadyExists
case codes.PermissionDenied:
return PermissionDenied
case codes.ResourceExhausted:
return ResourceExhausted
case codes.FailedPrecondition:
return FailedPrecondition
case codes.Aborted:
return Aborted
case codes.OutOfRange:
return OutOfRange
case codes.Unimplemented:
return Unimplemented
case codes.Internal:
return Internal
case codes.Unavailable:
return Unavailable
case codes.DataLoss:
return DataLoss
case codes.Unauthenticated:
return Unauthenticated
default:
return Unknown
}
}
func (e ErrorCode) ToGRPC() codes.Code {
switch e {
case OK:
return codes.OK
case Canceled:
return codes.Canceled
case Unknown:
return codes.Unknown
case InvalidArgument, MalformedRequest:
return codes.InvalidArgument
case DeadlineExceeded:
return codes.DeadlineExceeded
case NotFound:
return codes.NotFound
case AlreadyExists:
return codes.AlreadyExists
case PermissionDenied:
return codes.PermissionDenied
case ResourceExhausted:
return codes.ResourceExhausted
case FailedPrecondition:
return codes.FailedPrecondition
case Aborted:
return codes.Aborted
case OutOfRange:
return codes.OutOfRange
case Unimplemented:
return codes.Unimplemented
case MalformedResponse, Internal:
return codes.Internal
case Unavailable:
return codes.Unavailable
case DataLoss:
return codes.DataLoss
case Unauthenticated:
return codes.Unauthenticated
case UpstreamServerError:
return codes.Internal
case UpstreamClientError:
return codes.InvalidArgument
default:
return codes.Unknown
}
}
func (e ErrorCode) ToTwirp() twirp.ErrorCode {
switch e {
case OK:
return twirp.NoError
case Canceled:
return twirp.Canceled
case Unknown:
return twirp.Unknown
case InvalidArgument:
return twirp.InvalidArgument
case MalformedRequest, MalformedResponse:
return twirp.Malformed
case DeadlineExceeded:
return twirp.DeadlineExceeded
case NotFound:
return twirp.NotFound
case AlreadyExists:
return twirp.AlreadyExists
case PermissionDenied:
return twirp.PermissionDenied
case ResourceExhausted:
return twirp.ResourceExhausted
case FailedPrecondition:
return twirp.FailedPrecondition
case Aborted:
return twirp.Aborted
case OutOfRange:
return twirp.OutOfRange
case Unimplemented:
return twirp.Unimplemented
case Internal:
return twirp.Internal
case Unavailable:
return twirp.Unavailable
case DataLoss:
return twirp.DataLoss
case Unauthenticated:
return twirp.Unauthenticated
case UpstreamServerError:
return twirp.Internal
case UpstreamClientError:
return twirp.InvalidArgument
default:
return twirp.Unknown
}
}
func NewError(code ErrorCode, err error, details ...proto.Message) Error {
if err == nil {
panic("error is nil")
}
var protoDetails []*anypb.Any
for _, e := range details {
if p, err := anypb.New(e); err == nil {
protoDetails = append(protoDetails, p)
}
}
return &psrpcError{
error: err,
code: code,
details: protoDetails,
}
}
func NewErrorf(code ErrorCode, msg string, args ...interface{}) Error {
return &psrpcError{
error: fmt.Errorf(msg, args...),
code: code,
}
}
func NewErrorFromResponse(code, err string, details ...*anypb.Any) Error {
if code == "" {
code = string(Unknown)
}
return &psrpcError{
error: errors.New(err),
code: ErrorCode(code),
details: details,
}
}
const (
OK ErrorCode = ""
// Request Canceled by client
Canceled ErrorCode = "canceled"
// Could not unmarshal request
MalformedRequest ErrorCode = "malformed_request"
// Could not unmarshal result
MalformedResponse ErrorCode = "malformed_result"
// Request timed out
DeadlineExceeded ErrorCode = "deadline_exceeded"
// Service unavailable due to load and/or affinity constraints
Unavailable ErrorCode = "unavailable"
// Unknown (server returned non-psrpc error)
Unknown ErrorCode = "unknown"
// Invalid argument in request
InvalidArgument ErrorCode = "invalid_argument"
// Entity not found
NotFound ErrorCode = "not_found"
// Cannot produce and entity matching requested format
NotAcceptable ErrorCode = "not_acceptable"
// Duplicate creation attempted
AlreadyExists ErrorCode = "already_exists"
// Caller does not have required permissions
PermissionDenied ErrorCode = "permission_denied"
// Some resource has been exhausted, e.g. memory or quota
ResourceExhausted ErrorCode = "resource_exhausted"
// Inconsistent state to carry out request
FailedPrecondition ErrorCode = "failed_precondition"
// Request aborted
Aborted ErrorCode = "aborted"
// Operation was out of range
OutOfRange ErrorCode = "out_of_range"
// Operation is not implemented by the server
Unimplemented ErrorCode = "unimplemented"
// Operation failed due to an internal error
Internal ErrorCode = "internal"
// Irrecoverable loss or corruption of data
DataLoss ErrorCode = "data_loss"
// Similar to PermissionDenied, used when the caller is unidentified
Unauthenticated ErrorCode = "unauthenticated"
// Cannot consume the entity in the given format
UnprocessableEntity ErrorCode = "unprocessable_entity"
// Upstream server error
UpstreamServerError ErrorCode = "upstream_server_error"
// Upstread client error
UpstreamClientError ErrorCode = "upstream_client_error"
)
type psrpcError struct {
error
code ErrorCode
details []*anypb.Any
}
func (e psrpcError) Code() ErrorCode {
return e.code
}
func (e psrpcError) ToHttp() int {
return e.code.ToHTTP()
}
func (e psrpcError) DetailsProto() []*anypb.Any {
return e.details
}
func (e psrpcError) Details() []any {
return e.GRPCStatus().Details()
}
func (e psrpcError) GRPCStatus() *status.Status {
return status.FromProto(&spb.Status{
Code: int32(e.code.ToGRPC()),
Message: e.Error(),
Details: e.details,
})
}
func (e psrpcError) toTwirp() twirp.Error {
return twirp.NewError(e.code.ToTwirp(), e.Error())
}
func (e psrpcError) As(target any) bool {
switch te := target.(type) {
case *twirp.Error:
*te = e.toTwirp()
return true
}
return false
}
func (e psrpcError) Unwrap() []error {
return []error{e.error, e.code}
}