1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package errdefs
18
19import (
20	"context"
21	"strings"
22
23	"github.com/pkg/errors"
24	"google.golang.org/grpc/codes"
25	"google.golang.org/grpc/status"
26)
27
28// ToGRPC will attempt to map the backend containerd error into a grpc error,
29// using the original error message as a description.
30//
31// Further information may be extracted from certain errors depending on their
32// type.
33//
34// If the error is unmapped, the original error will be returned to be handled
35// by the regular grpc error handling stack.
36func ToGRPC(err error) error {
37	if err == nil {
38		return nil
39	}
40
41	if isGRPCError(err) {
42		// error has already been mapped to grpc
43		return err
44	}
45
46	switch {
47	case IsInvalidArgument(err):
48		return status.Errorf(codes.InvalidArgument, err.Error())
49	case IsNotFound(err):
50		return status.Errorf(codes.NotFound, err.Error())
51	case IsAlreadyExists(err):
52		return status.Errorf(codes.AlreadyExists, err.Error())
53	case IsFailedPrecondition(err):
54		return status.Errorf(codes.FailedPrecondition, err.Error())
55	case IsUnavailable(err):
56		return status.Errorf(codes.Unavailable, err.Error())
57	case IsNotImplemented(err):
58		return status.Errorf(codes.Unimplemented, err.Error())
59	case IsCanceled(err):
60		return status.Errorf(codes.Canceled, err.Error())
61	case IsDeadlineExceeded(err):
62		return status.Errorf(codes.DeadlineExceeded, err.Error())
63	}
64
65	return err
66}
67
68// ToGRPCf maps the error to grpc error codes, assembling the formatting string
69// and combining it with the target error string.
70//
71// This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...))
72func ToGRPCf(err error, format string, args ...interface{}) error {
73	return ToGRPC(errors.Wrapf(err, format, args...))
74}
75
76// FromGRPC returns the underlying error from a grpc service based on the grpc error code
77func FromGRPC(err error) error {
78	if err == nil {
79		return nil
80	}
81
82	var cls error // divide these into error classes, becomes the cause
83
84	switch code(err) {
85	case codes.InvalidArgument:
86		cls = ErrInvalidArgument
87	case codes.AlreadyExists:
88		cls = ErrAlreadyExists
89	case codes.NotFound:
90		cls = ErrNotFound
91	case codes.Unavailable:
92		cls = ErrUnavailable
93	case codes.FailedPrecondition:
94		cls = ErrFailedPrecondition
95	case codes.Unimplemented:
96		cls = ErrNotImplemented
97	case codes.Canceled:
98		cls = context.Canceled
99	case codes.DeadlineExceeded:
100		cls = context.DeadlineExceeded
101	default:
102		cls = ErrUnknown
103	}
104
105	msg := rebaseMessage(cls, err)
106	if msg != "" {
107		err = errors.Wrap(cls, msg)
108	} else {
109		err = errors.WithStack(cls)
110	}
111
112	return err
113}
114
115// rebaseMessage removes the repeats for an error at the end of an error
116// string. This will happen when taking an error over grpc then remapping it.
117//
118// Effectively, we just remove the string of cls from the end of err if it
119// appears there.
120func rebaseMessage(cls error, err error) string {
121	desc := errDesc(err)
122	clss := cls.Error()
123	if desc == clss {
124		return ""
125	}
126
127	return strings.TrimSuffix(desc, ": "+clss)
128}
129
130func isGRPCError(err error) bool {
131	_, ok := status.FromError(err)
132	return ok
133}
134
135func code(err error) codes.Code {
136	if s, ok := status.FromError(err); ok {
137		return s.Code()
138	}
139	return codes.Unknown
140}
141
142func errDesc(err error) string {
143	if s, ok := status.FromError(err); ok {
144		return s.Message()
145	}
146	return err.Error()
147}
148