peridot/vendor/go.temporal.io/api/serviceerror/convert.go

155 lines
5.0 KiB
Go
Raw Permalink Normal View History

2022-07-07 20:11:50 +00:00
// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package serviceerror
import (
"context"
"errors"
"github.com/gogo/status"
"google.golang.org/grpc/codes"
"go.temporal.io/api/errordetails/v1"
)
// ToStatus converts service error to gogo gRPC Status.
// If error is not a service error it returns status with code Unknown.
func ToStatus(err error) *status.Status {
if err == nil {
return status.New(codes.OK, "")
}
if svcerr, ok := err.(ServiceError); ok {
return svcerr.Status()
}
// Special case for context.DeadlineExceeded and context.Canceled because they can happen in unpredictable places.
if errors.Is(err, context.DeadlineExceeded) {
return status.New(codes.DeadlineExceeded, err.Error())
}
if errors.Is(err, context.Canceled) {
return status.New(codes.Canceled, err.Error())
}
// Internal logic of status.Convert is:
// - if err is already gogo Status or gRPC Status, then just return it (this should never happen though).
// - otherwise returns codes.Unknown with message from err.Error() (this might happen if some generic go error reach to this point).
return status.Convert(err)
}
// FromStatus converts gogo gRPC Status to service error.
func FromStatus(st *status.Status) error {
if st == nil || st.Code() == codes.OK {
return nil
}
// Simple case. Code to serviceerror is one to one mapping and there are no error details.
switch st.Code() {
case codes.Internal:
return newInternal(st)
case codes.DataLoss:
return newDataLoss(st)
case codes.ResourceExhausted:
return newResourceExhausted(st)
case codes.DeadlineExceeded:
return newDeadlineExceeded(st)
case codes.Canceled:
return newCanceled(st)
case codes.Unavailable:
return newUnavailable(st)
case codes.Unimplemented:
return newUnimplemented(st)
case codes.Unknown:
// Unwrap error message from unknown error.
return errors.New(st.Message())
// Unsupported codes.
case codes.OutOfRange,
codes.Unauthenticated:
// Use standard gRPC error representation for unsupported codes ("rpc error: code = %s desc = %s").
return st.Err()
}
errDetails := extractErrorDetails(st)
// If there was an error during details extraction, it will go to errDetails.
if err, ok := errDetails.(error); ok {
return NewInvalidArgument(err.Error())
}
switch st.Code() {
case codes.NotFound:
if errDetails == nil {
return newNotFound(st, nil)
}
switch errDetails := errDetails.(type) {
case *errordetails.NotFoundFailure:
return newNotFound(st, errDetails)
}
case codes.InvalidArgument:
if errDetails == nil {
return newInvalidArgument(st)
}
switch errDetails.(type) {
case *errordetails.QueryFailedFailure:
return newQueryFailed(st)
}
case codes.AlreadyExists:
switch errDetails := errDetails.(type) {
case *errordetails.NamespaceAlreadyExistsFailure:
return newNamespaceAlreadyExists(st)
case *errordetails.WorkflowExecutionAlreadyStartedFailure:
return newWorkflowExecutionAlreadyStarted(st, errDetails)
case *errordetails.CancellationAlreadyRequestedFailure:
return newCancellationAlreadyRequested(st)
}
case codes.FailedPrecondition:
switch errDetails := errDetails.(type) {
case *errordetails.NamespaceNotActiveFailure:
return newNamespaceNotActive(st, errDetails)
case *errordetails.ClientVersionNotSupportedFailure:
return newClientVersionNotSupported(st, errDetails)
case *errordetails.ServerVersionNotSupportedFailure:
return newServerVersionNotSupported(st, errDetails)
}
case codes.PermissionDenied:
switch errDetails := errDetails.(type) {
case *errordetails.PermissionDeniedFailure:
return newPermissionDenied(st, errDetails)
}
return newPermissionDenied(st, nil)
}
// st.Code() should have error details but it didn't (or error details are of a wrong type).
// Then use standard gRPC error representation ("rpc error: code = %s desc = %s").
return st.Err()
}
func extractErrorDetails(st *status.Status) interface{} {
details := st.Details()
if len(details) > 0 {
return details[0]
}
return nil
}