@@ -2,14 +2,17 @@ package cmd
22
33import (
44 "errors"
5+ "fmt"
56 "log/slog"
67
78 "github.com/gogo/googleapis/google/rpc"
9+ "google.golang.org/genproto/googleapis/rpc/errdetails"
810 "google.golang.org/grpc/codes"
911 "google.golang.org/grpc/status"
1012 "google.golang.org/protobuf/protoadapt"
1113
1214 "github.com/PeerDB-io/peerdb/flow/generated/grpc_handler"
15+ "github.com/PeerDB-io/peerdb/flow/pkg/common"
1316)
1417
1518// APIError is a strongly-typed error that must be a gRPC status error.
@@ -24,31 +27,31 @@ func newAPIError(s *status.Status) *apiError {
2427 return & apiError {status : s }
2528}
2629
27- func NewInvalidArgumentApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
30+ func NewInvalidArgumentApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
2831 return newAPIError (convertToStatus (codes .InvalidArgument , err , details ... ))
2932}
3033
31- func NewFailedPreconditionApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
34+ func NewFailedPreconditionApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
3235 return newAPIError (convertToStatus (codes .FailedPrecondition , err , details ... ))
3336}
3437
35- func NewInternalApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
38+ func NewInternalApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
3639 return newAPIError (convertToStatus (codes .Internal , err , details ... ))
3740}
3841
39- func NewUnavailableApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
42+ func NewUnavailableApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
4043 return newAPIError (convertToStatus (codes .Unavailable , err , details ... ))
4144}
4245
43- func NewUnimplementedApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
46+ func NewUnimplementedApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
4447 return newAPIError (convertToStatus (codes .Unimplemented , err , details ... ))
4548}
4649
47- func NewAlreadyExistsApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
50+ func NewAlreadyExistsApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
4851 return newAPIError (convertToStatus (codes .AlreadyExists , err , details ... ))
4952}
5053
51- func NewNotFoundApiError (err error , details ... * rpc. ErrorInfo ) * apiError {
54+ func NewNotFoundApiError (err error , details ... protoadapt. MessageV1 ) * apiError {
5255 return newAPIError (convertToStatus (codes .NotFound , err , details ... ))
5356}
5457
@@ -70,18 +73,14 @@ func (e *apiError) Code() codes.Code {
7073 return e .status .Code ()
7174}
7275
73- func convertToStatus (code codes.Code , err error , details ... * rpc. ErrorInfo ) * status.Status {
76+ func convertToStatus (code codes.Code , err error , details ... protoadapt. MessageV1 ) * status.Status {
7477 errorStatus := status .New (code , err .Error ())
7578 if len (details ) == 0 {
7679 return errorStatus
7780 }
78- convertedDetails := make ([]protoadapt.MessageV1 , len (details ))
79- for i , detail := range details {
80- convertedDetails [i ] = detail
81- }
82- richStatus , err := errorStatus .WithDetails (convertedDetails ... )
81+ richStatus , err := errorStatus .WithDetails (details ... )
8382 if err != nil {
84- // This cannot happen because we control all calls to convertToStatus and only pass code != OK and allow only rpc.ErrorInfo in details
83+ // This cannot happen because we control all calls to convertToStatus and only pass code != OK and valid proto details
8584 slog .Error ("Failed to convert to grpc proto error" , slog .Any ("error" , err )) //nolint:sloglint // No context in conversion helper
8685 return errorStatus
8786 }
@@ -107,34 +106,57 @@ func AsAPIError(err error) APIError {
107106 return NewInternalApiError (err )
108107}
109108
110- const (
111- ErrorInfoReasonClickHousePeer = "CLICKHOUSE_PEER"
112- ErrorInfoReasonMirror = "MIRROR"
113- )
109+ func NewMirrorErrorInfo (metadata map [string ]string ) * rpc.ErrorInfo {
110+ return & rpc.ErrorInfo {
111+ Reason : common .ErrorInfoReasonMirror ,
112+ Domain : common .ErrorInfoDomain ,
113+ Metadata : metadata ,
114+ }
115+ }
114116
115- const (
116- ErrorInfoDomain = "peerdb.io"
117- )
117+ func NewSourceTableMissingErrorInfo () * rpc.ErrorInfo {
118+ return & rpc.ErrorInfo {
119+ Reason : common .ErrorInfoReasonSourceTableMissing ,
120+ Domain : common .ErrorInfoDomain ,
121+ }
122+ }
118123
119- const (
120- ErrorMetadataDownstreamErrorCode = "downstreamErrorCode"
121- ErrorMetadataOffendingField = "offendingField"
122- )
124+ // NewSourceTableMissingPreconditionFailure builds the per-table breakdown detail
125+ // that accompanies the SOURCE_TABLE_MISSING ErrorInfo on FailedPrecondition.
126+ func NewSourceTableMissingPreconditionFailure (tables []common.QualifiedTable ) protoadapt.MessageV1 {
127+ violations := make ([]* errdetails.PreconditionFailure_Violation , len (tables ))
128+ for i , t := range tables {
129+ subject := fmt .Sprintf ("%s.%s" , t .Namespace , t .Table )
130+ violations [i ] = & errdetails.PreconditionFailure_Violation {
131+ Type : common .ErrorInfoReasonSourceTableMissing ,
132+ Subject : subject ,
133+ Description : fmt .Sprintf ("source table %s does not exist" , subject ),
134+ }
135+ }
136+ return protoadapt .MessageV1Of (& errdetails.PreconditionFailure {Violations : violations })
137+ }
123138
124- func NewClickHousePeerErrorInfo ( metadata map [ string ] string ) * rpc.ErrorInfo {
139+ func NewTablesNotInPublicationErrorInfo ( publication string ) * rpc.ErrorInfo {
125140 return & rpc.ErrorInfo {
126- Reason : ErrorInfoReasonClickHousePeer ,
127- Domain : ErrorInfoDomain ,
128- Metadata : metadata ,
141+ Reason : common . ErrorInfoReasonTablesNotInPublication ,
142+ Domain : common . ErrorInfoDomain ,
143+ Metadata : map [ string ] string { common . ErrorMetadataPublication : publication } ,
129144 }
130145}
131146
132- func NewMirrorErrorInfo (metadata map [string ]string ) * rpc.ErrorInfo {
133- return & rpc.ErrorInfo {
134- Reason : ErrorInfoReasonMirror ,
135- Domain : ErrorInfoDomain ,
136- Metadata : metadata ,
147+ // NewTablesNotInPublicationPreconditionFailure builds the per-table breakdown detail
148+ // that accompanies the TABLES_NOT_IN_PUBLICATION ErrorInfo on FailedPrecondition.
149+ func NewTablesNotInPublicationPreconditionFailure (publication string , tables []common.QualifiedTable ) protoadapt.MessageV1 {
150+ violations := make ([]* errdetails.PreconditionFailure_Violation , len (tables ))
151+ for i , t := range tables {
152+ subject := fmt .Sprintf ("%s.%s" , t .Namespace , t .Table )
153+ violations [i ] = & errdetails.PreconditionFailure_Violation {
154+ Type : common .ErrorInfoReasonTablesNotInPublication ,
155+ Subject : subject ,
156+ Description : fmt .Sprintf ("table %s is not in publication %q" , subject , publication ),
157+ }
137158 }
159+ return protoadapt .MessageV1Of (& errdetails.PreconditionFailure {Violations : violations })
138160}
139161
140162var ErrUnderMaintenance = errors .New ("PeerDB is under maintenance. Please retry in a few minutes" )
0 commit comments