-
Notifications
You must be signed in to change notification settings - Fork 198
Expand file tree
/
Copy pathapi_error.go
More file actions
162 lines (134 loc) · 5.2 KB
/
Copy pathapi_error.go
File metadata and controls
162 lines (134 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package cmd
import (
"errors"
"fmt"
"log/slog"
"github.com/gogo/googleapis/google/rpc"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/protoadapt"
"github.com/PeerDB-io/peerdb/flow/generated/grpc_handler"
"github.com/PeerDB-io/peerdb/flow/pkg/common"
)
// APIError is a strongly-typed error that must be a gRPC status error.
// All handler methods should return this type instead of the generic error interface.
type APIError = grpc_handler.APIError
type apiError struct {
status *status.Status
}
func newAPIError(s *status.Status) *apiError {
return &apiError{status: s}
}
func NewInvalidArgumentApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.InvalidArgument, err, details...))
}
func NewFailedPreconditionApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.FailedPrecondition, err, details...))
}
func NewInternalApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.Internal, err, details...))
}
func NewUnavailableApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.Unavailable, err, details...))
}
func NewUnimplementedApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.Unimplemented, err, details...))
}
func NewAlreadyExistsApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.AlreadyExists, err, details...))
}
func NewNotFoundApiError(err error, details ...protoadapt.MessageV1) *apiError {
return newAPIError(convertToStatus(codes.NotFound, err, details...))
}
func (e *apiError) Error() string {
if e.status == nil {
return "unknown error"
}
return e.status.Err().Error()
}
func (e *apiError) GRPCStatus() *status.Status {
return e.status
}
func (e *apiError) Code() codes.Code {
if e.status == nil {
return codes.Unknown
}
return e.status.Code()
}
func convertToStatus(code codes.Code, err error, details ...protoadapt.MessageV1) *status.Status {
errorStatus := status.New(code, err.Error())
if len(details) == 0 {
return errorStatus
}
richStatus, err := errorStatus.WithDetails(details...)
if err != nil {
// This cannot happen because we control all calls to convertToStatus and only pass code != OK and valid proto details
slog.Error("Failed to convert to grpc proto error", slog.Any("error", err)) //nolint:sloglint // No context in conversion helper
return errorStatus
}
return richStatus
}
// AsAPIError converts an error to APIError if it's a gRPC status error,
// otherwise wraps it as an Internal error
func AsAPIError(err error) APIError {
if err == nil {
return nil
}
if apiErr, ok := err.(APIError); ok {
return apiErr
}
if s, ok := status.FromError(err); ok {
return newAPIError(s)
}
return NewInternalApiError(err)
}
func NewMirrorErrorInfo(metadata map[string]string) *rpc.ErrorInfo {
return &rpc.ErrorInfo{
Reason: common.ErrorInfoReasonMirror,
Domain: common.ErrorInfoDomain,
Metadata: metadata,
}
}
func NewSourceTableMissingErrorInfo() *rpc.ErrorInfo {
return &rpc.ErrorInfo{
Reason: common.ErrorInfoReasonSourceTableMissing,
Domain: common.ErrorInfoDomain,
}
}
// NewSourceTableMissingPreconditionFailure builds the per-table breakdown detail
// that accompanies the SOURCE_TABLE_MISSING ErrorInfo on FailedPrecondition.
func NewSourceTableMissingPreconditionFailure(tables []common.QualifiedTable) protoadapt.MessageV1 {
violations := make([]*errdetails.PreconditionFailure_Violation, len(tables))
for i, t := range tables {
subject := fmt.Sprintf("%s.%s", t.Namespace, t.Table)
violations[i] = &errdetails.PreconditionFailure_Violation{
Type: common.ErrorInfoReasonSourceTableMissing,
Subject: subject,
Description: fmt.Sprintf("source table %s does not exist", subject),
}
}
return protoadapt.MessageV1Of(&errdetails.PreconditionFailure{Violations: violations})
}
func NewTablesNotInPublicationErrorInfo(publication string) *rpc.ErrorInfo {
return &rpc.ErrorInfo{
Reason: common.ErrorInfoReasonTablesNotInPublication,
Domain: common.ErrorInfoDomain,
Metadata: map[string]string{common.ErrorMetadataPublication: publication},
}
}
// NewTablesNotInPublicationPreconditionFailure builds the per-table breakdown detail
// that accompanies the TABLES_NOT_IN_PUBLICATION ErrorInfo on FailedPrecondition.
func NewTablesNotInPublicationPreconditionFailure(publication string, tables []common.QualifiedTable) protoadapt.MessageV1 {
violations := make([]*errdetails.PreconditionFailure_Violation, len(tables))
for i, t := range tables {
subject := fmt.Sprintf("%s.%s", t.Namespace, t.Table)
violations[i] = &errdetails.PreconditionFailure_Violation{
Type: common.ErrorInfoReasonTablesNotInPublication,
Subject: subject,
Description: fmt.Sprintf("table %s is not in publication %q", subject, publication),
}
}
return protoadapt.MessageV1Of(&errdetails.PreconditionFailure{Violations: violations})
}
var ErrUnderMaintenance = errors.New("PeerDB is under maintenance. Please retry in a few minutes")