github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/shared/errors.go (about) 1 package shared 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 9 "github.com/rs/zerolog" 10 "google.golang.org/genproto/googleapis/rpc/errdetails" 11 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/status" 13 14 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 15 16 "github.com/authzed/spicedb/internal/dispatch" 17 "github.com/authzed/spicedb/internal/graph" 18 log "github.com/authzed/spicedb/internal/logging" 19 "github.com/authzed/spicedb/internal/sharederrors" 20 "github.com/authzed/spicedb/pkg/cursor" 21 "github.com/authzed/spicedb/pkg/datastore" 22 dispatchv1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 23 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 24 "github.com/authzed/spicedb/pkg/spiceerrors" 25 "github.com/authzed/spicedb/pkg/typesystem" 26 ) 27 28 // ErrServiceReadOnly is an extended GRPC error returned when a service is in read-only mode. 29 var ErrServiceReadOnly = mustMakeStatusReadonly() 30 31 func mustMakeStatusReadonly() error { 32 status, err := status.New(codes.Unavailable, "service read-only").WithDetails(&errdetails.ErrorInfo{ 33 Reason: v1.ErrorReason_name[int32(v1.ErrorReason_ERROR_REASON_SERVICE_READ_ONLY)], 34 Domain: spiceerrors.Domain, 35 }) 36 if err != nil { 37 panic("error constructing shared error type") 38 } 39 return status.Err() 40 } 41 42 // NewSchemaWriteDataValidationError creates a new error representing that a schema write cannot be 43 // completed due to existing data that would be left unreferenced. 44 func NewSchemaWriteDataValidationError(message string, args ...any) ErrSchemaWriteDataValidation { 45 return ErrSchemaWriteDataValidation{ 46 error: fmt.Errorf(message, args...), 47 } 48 } 49 50 // ErrSchemaWriteDataValidation occurs when a schema cannot be applied due to leaving data unreferenced. 51 type ErrSchemaWriteDataValidation struct { 52 error 53 } 54 55 // MarshalZerologObject implements zerolog object marshalling. 56 func (err ErrSchemaWriteDataValidation) MarshalZerologObject(e *zerolog.Event) { 57 e.Err(err.error) 58 } 59 60 // GRPCStatus implements retrieving the gRPC status for the error. 61 func (err ErrSchemaWriteDataValidation) GRPCStatus() *status.Status { 62 return spiceerrors.WithCodeAndDetails( 63 err, 64 codes.InvalidArgument, 65 spiceerrors.ForReason( 66 v1.ErrorReason_ERROR_REASON_SCHEMA_TYPE_ERROR, 67 map[string]string{}, 68 ), 69 ) 70 } 71 72 // MaxDepthExceededError is an error returned when the maximum depth for dispatching has been exceeded. 73 type MaxDepthExceededError struct { 74 error 75 76 // AllowedMaximumDepth is the configured allowed maximum depth. 77 AllowedMaximumDepth uint32 78 } 79 80 // GRPCStatus implements retrieving the gRPC status for the error. 81 func (err MaxDepthExceededError) GRPCStatus() *status.Status { 82 return spiceerrors.WithCodeAndDetails( 83 err, 84 codes.ResourceExhausted, 85 spiceerrors.ForReason( 86 v1.ErrorReason_ERROR_REASON_MAXIMUM_DEPTH_EXCEEDED, 87 map[string]string{ 88 "maximum_depth_allowed": strconv.Itoa(int(err.AllowedMaximumDepth)), 89 }, 90 ), 91 ) 92 } 93 94 // NewMaxDepthExceededError creates a new MaxDepthExceededError. 95 func NewMaxDepthExceededError(allowedMaximumDepth uint32, isCheckRequest bool) error { 96 if isCheckRequest { 97 return MaxDepthExceededError{ 98 fmt.Errorf("the check request has exceeded the allowable maximum depth of %d: this usually indicates a recursive or too deep data dependency. Try running zed with --explain to see the dependency. See: https://spicedb.dev/d/debug-max-depth-check", allowedMaximumDepth), 99 allowedMaximumDepth, 100 } 101 } 102 103 return MaxDepthExceededError{ 104 fmt.Errorf("the request has exceeded the allowable maximum depth of %d: this usually indicates a recursive or too deep data dependency. See: https://spicedb.dev/d/debug-max-depth", allowedMaximumDepth), 105 allowedMaximumDepth, 106 } 107 } 108 109 func AsValidationError(err error) *ErrSchemaWriteDataValidation { 110 var validationErr ErrSchemaWriteDataValidation 111 if errors.As(err, &validationErr) { 112 return &validationErr 113 } 114 return nil 115 } 116 117 type ConfigForErrors struct { 118 MaximumAPIDepth uint32 119 } 120 121 func RewriteErrorWithoutConfig(ctx context.Context, err error) error { 122 return RewriteError(ctx, err, nil) 123 } 124 125 func RewriteError(ctx context.Context, err error, config *ConfigForErrors) error { 126 // Check if the error can be directly used. 127 if _, ok := status.FromError(err); ok { 128 return err 129 } 130 131 // Otherwise, convert any graph/datastore errors. 132 var nsNotFoundError sharederrors.UnknownNamespaceError 133 var relationNotFoundError sharederrors.UnknownRelationError 134 135 var compilerError compiler.BaseCompilerError 136 var sourceError spiceerrors.ErrorWithSource 137 var typeError typesystem.TypeError 138 var maxDepthError dispatch.MaxDepthExceededError 139 140 switch { 141 case errors.As(err, &typeError): 142 return spiceerrors.WithCodeAndReason(err, codes.FailedPrecondition, v1.ErrorReason_ERROR_REASON_SCHEMA_TYPE_ERROR) 143 case errors.As(err, &compilerError): 144 return spiceerrors.WithCodeAndReason(err, codes.InvalidArgument, v1.ErrorReason_ERROR_REASON_SCHEMA_PARSE_ERROR) 145 case errors.As(err, &sourceError): 146 return spiceerrors.WithCodeAndReason(err, codes.InvalidArgument, v1.ErrorReason_ERROR_REASON_SCHEMA_PARSE_ERROR) 147 148 case errors.Is(err, cursor.ErrHashMismatch): 149 return spiceerrors.WithCodeAndReason(err, codes.FailedPrecondition, v1.ErrorReason_ERROR_REASON_INVALID_CURSOR) 150 151 case errors.As(err, &nsNotFoundError): 152 return spiceerrors.WithCodeAndReason(err, codes.FailedPrecondition, v1.ErrorReason_ERROR_REASON_UNKNOWN_DEFINITION) 153 case errors.As(err, &relationNotFoundError): 154 return spiceerrors.WithCodeAndReason(err, codes.FailedPrecondition, v1.ErrorReason_ERROR_REASON_UNKNOWN_RELATION_OR_PERMISSION) 155 156 case errors.As(err, &maxDepthError): 157 if config == nil { 158 return spiceerrors.MustBugf("missing config for API error") 159 } 160 161 _, isCheckRequest := maxDepthError.Request.(*dispatchv1.DispatchCheckRequest) 162 return NewMaxDepthExceededError(config.MaximumAPIDepth, isCheckRequest) 163 164 case errors.As(err, &datastore.ErrReadOnly{}): 165 return ErrServiceReadOnly 166 case errors.As(err, &datastore.ErrInvalidRevision{}): 167 return status.Errorf(codes.OutOfRange, "invalid zedtoken: %s", err) 168 case errors.As(err, &datastore.ErrReadOnly{}): 169 return ErrServiceReadOnly 170 case errors.As(err, &datastore.ErrCaveatNameNotFound{}): 171 return spiceerrors.WithCodeAndReason(err, codes.FailedPrecondition, v1.ErrorReason_ERROR_REASON_UNKNOWN_CAVEAT) 172 case errors.As(err, &datastore.ErrWatchDisabled{}): 173 return status.Errorf(codes.FailedPrecondition, "%s", err) 174 175 case errors.As(err, &graph.ErrInvalidArgument{}): 176 return status.Errorf(codes.InvalidArgument, "%s", err) 177 case errors.As(err, &graph.ErrRelationMissingTypeInfo{}): 178 return status.Errorf(codes.FailedPrecondition, "failed precondition: %s", err) 179 case errors.As(err, &graph.ErrAlwaysFail{}): 180 log.Ctx(ctx).Err(err).Msg("received internal error") 181 return status.Errorf(codes.Internal, "internal error: %s", err) 182 case errors.As(err, &graph.ErrUnimplemented{}): 183 return status.Errorf(codes.Unimplemented, "%s", err) 184 case errors.Is(err, context.DeadlineExceeded): 185 return status.Errorf(codes.DeadlineExceeded, "%s", err) 186 case errors.Is(err, context.Canceled): 187 err := context.Cause(ctx) 188 if err != nil { 189 if _, ok := status.FromError(err); ok { 190 return err 191 } 192 } 193 194 return status.Errorf(codes.Canceled, "%s", err) 195 default: 196 log.Ctx(ctx).Err(err).Msg("received unexpected error") 197 return err 198 } 199 }