mirror of
https://github.com/hanzoai/psrpc.git
synced 2026-09-30 23:47:22 +00:00
376 lines
9.3 KiB
Go
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}
|
|
}
|