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	"strings"
21
22	"github.com/pkg/errors"
23	"google.golang.org/grpc/codes"
24	"google.golang.org/grpc/status"
25)
26
27// ToGRPC will attempt to map the backend containerd error into a grpc error,
28// using the original error message as a description.
29//
30// Further information may be extracted from certain errors depending on their
31// type.
32//
33// If the error is unmapped, the original error will be returned to be handled
34// by the regular grpc error handling stack.
35func ToGRPC(err error) error {
36	if err == nil {
37		return nil
38	}
39
40	if isGRPCError(err) {
41		// error has already been mapped to grpc
42		return err
43	}
44
45	switch {
46	case IsInvalidArgument(err):
47		return status.Errorf(codes.InvalidArgument, err.Error())
48	case IsNotFound(err):
49		return status.Errorf(codes.NotFound, err.Error())
50	case IsAlreadyExists(err):
51		return status.Errorf(codes.AlreadyExists, err.Error())
52	case IsFailedPrecondition(err):
53		return status.Errorf(codes.FailedPrecondition, err.Error())
54	case IsUnavailable(err):
55		return status.Errorf(codes.Unavailable, err.Error())
56	case IsNotImplemented(err):
57		return status.Errorf(codes.Unimplemented, err.Error())
58	}
59
60	return err
61}
62
63// ToGRPCf maps the error to grpc error codes, assembling the formatting string
64// and combining it with the target error string.
65//
66// This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...))
67func ToGRPCf(err error, format string, args ...interface{}) error {
68	return ToGRPC(errors.Wrapf(err, format, args...))
69}
70
71// FromGRPC returns the underlying error from a grpc service based on the grpc error code
72func FromGRPC(err error) error {
73	if err == nil {
74		return nil
75	}
76
77	var cls error // divide these into error classes, becomes the cause
78
79	switch code(err) {
80	case codes.InvalidArgument:
81		cls = ErrInvalidArgument
82	case codes.AlreadyExists:
83		cls = ErrAlreadyExists
84	case codes.NotFound:
85		cls = ErrNotFound
86	case codes.Unavailable:
87		cls = ErrUnavailable
88	case codes.FailedPrecondition:
89		cls = ErrFailedPrecondition
90	case codes.Unimplemented:
91		cls = ErrNotImplemented
92	default:
93		cls = ErrUnknown
94	}
95
96	msg := rebaseMessage(cls, err)
97	if msg != "" {
98		err = errors.Wrapf(cls, msg)
99	} else {
100		err = errors.WithStack(cls)
101	}
102
103	return err
104}
105
106// rebaseMessage removes the repeats for an error at the end of an error
107// string. This will happen when taking an error over grpc then remapping it.
108//
109// Effectively, we just remove the string of cls from the end of err if it
110// appears there.
111func rebaseMessage(cls error, err error) string {
112	desc := errDesc(err)
113	clss := cls.Error()
114	if desc == clss {
115		return ""
116	}
117
118	return strings.TrimSuffix(desc, ": "+clss)
119}
120
121func isGRPCError(err error) bool {
122	_, ok := status.FromError(err)
123	return ok
124}
125
126func code(err error) codes.Code {
127	if s, ok := status.FromError(err); ok {
128		return s.Code()
129	}
130	return codes.Unknown
131}
132
133func errDesc(err error) string {
134	if s, ok := status.FromError(err); ok {
135		return s.Message()
136	}
137	return err.Error()
138}
139