Browse code

Use the new error package

This is the first step in converting out static strings into well-defined
error types. This shows just a few examples of it to get a feel for how things
will look. Once we agree on the basic outline we can then work on converting
the rest of the code over.

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2015/07/22 05:30:32
Showing 9 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,58 @@
0
+Docker 'errors' package
1
+=======================
2
+
3
+This package contains all of the error messages generated by the Docker
4
+engine that might be exposed via the Docker engine's REST API.
5
+
6
+Each top-level engine package will have its own file in this directory
7
+so that there's a clear grouping of errors, instead of just one big
8
+file. The errors for each package are defined here instead of within
9
+their respective package structure so that Docker CLI code that may need
10
+to import these error definition files will not need to know or understand
11
+the engine's package/directory structure. In other words, all they should
12
+need to do is import `.../docker/api/errors` and they will automatically
13
+pick up all Docker engine defined errors.  This also gives the engine
14
+developers the freedom to change the engine packaging structure (e.g. to
15
+CRUD packages) without worrying about breaking existing clients.
16
+
17
+These errors are defined using the 'errcode' package. The `errcode`  package
18
+allows for each error to be typed and include all information necessary to
19
+have further processing done on them if necessary.  In particular, each error
20
+includes:
21
+
22
+* Value - a unique string (in all caps) associated with this error.
23
+Typically, this string is the same name as the variable name of the error
24
+(w/o the `ErrorCode` text) but in all caps.
25
+
26
+* Message - the human readable sentence that will be displayed for this
27
+error. It can contain '%s' substitutions that allows for the code generating
28
+the error to specify values that will be inserted in the string prior to
29
+being displayed to the end-user. The `WithArgs()` function can be used to
30
+specify the insertion strings.  Note, the evaluation of the strings will be
31
+done at the time `WithArgs()` is called.
32
+
33
+* Description - additional human readable text to further explain the
34
+circumstances of the error situation.
35
+
36
+* HTTPStatusCode - when the error is returned back to a CLI, this value
37
+will be used to populate the HTTP status code. If not present the default
38
+value will be `StatusInternalServerError`, 500.
39
+
40
+Not all errors generated within the engine's executable will be propagated
41
+back to the engine's API layer. For example, it is expected that errors
42
+generated by vendored code (under `docker/vendor`) and packaged code
43
+(under `docker/pkg`) will be converted into errors defined by this package.
44
+
45
+When processing an errcode error, if you are looking for a particular
46
+error then you can do something like:
47
+
48
+```
49
+import derr "github.com/docker/docker/api/errors"
50
+
51
+...
52
+
53
+err := someFunc()
54
+if err.ErrorCode() == derr.ErrorCodeNoSuchContainer {
55
+	...
56
+}
57
+```
0 58
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+package errors
1
+
2
+// This file contains all of the errors that can be generated from the
3
+// docker/builder component.
4
+
5
+import (
6
+	"net/http"
7
+
8
+	"github.com/docker/distribution/registry/api/errcode"
9
+)
10
+
11
+var (
12
+	// ErrorCodeAtLeastOneArg is generated when the parser comes across a
13
+	// Dockerfile command that doesn't have any args.
14
+	ErrorCodeAtLeastOneArg = errcode.Register(errGroup, errcode.ErrorDescriptor{
15
+		Value:          "ATLEASTONEARG",
16
+		Message:        "%s requires at least one argument",
17
+		Description:    "The specified command requires at least one argument",
18
+		HTTPStatusCode: http.StatusInternalServerError,
19
+	})
20
+
21
+	// ErrorCodeExactlyOneArg is generated when the parser comes across a
22
+	// Dockerfile command that requires exactly one arg but got less/more.
23
+	ErrorCodeExactlyOneArg = errcode.Register(errGroup, errcode.ErrorDescriptor{
24
+		Value:          "EXACTLYONEARG",
25
+		Message:        "%s requires exactly one argument",
26
+		Description:    "The specified command requires exactly one argument",
27
+		HTTPStatusCode: http.StatusInternalServerError,
28
+	})
29
+
30
+	// ErrorCodeAtLeastTwoArgs is generated when the parser comes across a
31
+	// Dockerfile command that requires at least two args but got less.
32
+	ErrorCodeAtLeastTwoArgs = errcode.Register(errGroup, errcode.ErrorDescriptor{
33
+		Value:          "ATLEASTTWOARGS",
34
+		Message:        "%s requires at least two arguments",
35
+		Description:    "The specified command requires at least two arguments",
36
+		HTTPStatusCode: http.StatusInternalServerError,
37
+	})
38
+
39
+	// ErrorCodeTooManyArgs is generated when the parser comes across a
40
+	// Dockerfile command that has more args than it should
41
+	ErrorCodeTooManyArgs = errcode.Register(errGroup, errcode.ErrorDescriptor{
42
+		Value:          "TOOMANYARGS",
43
+		Message:        "Bad input to %s, too many args",
44
+		Description:    "The specified command was passed too many arguments",
45
+		HTTPStatusCode: http.StatusInternalServerError,
46
+	})
47
+
48
+	// ErrorCodeChainOnBuild is generated when the parser comes across a
49
+	// Dockerfile command that is trying to chain ONBUILD commands.
50
+	ErrorCodeChainOnBuild = errcode.Register(errGroup, errcode.ErrorDescriptor{
51
+		Value:          "CHAINONBUILD",
52
+		Message:        "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
53
+		Description:    "ONBUILD Dockerfile commands aren't allow on ONBUILD commands",
54
+		HTTPStatusCode: http.StatusInternalServerError,
55
+	})
56
+
57
+	// ErrorCodeBadOnBuildCmd is generated when the parser comes across a
58
+	// an ONBUILD Dockerfile command with an invalid trigger/command.
59
+	ErrorCodeBadOnBuildCmd = errcode.Register(errGroup, errcode.ErrorDescriptor{
60
+		Value:          "BADONBUILDCMD",
61
+		Message:        "%s isn't allowed as an ONBUILD trigger",
62
+		Description:    "The specified ONBUILD command isn't allowed",
63
+		HTTPStatusCode: http.StatusInternalServerError,
64
+	})
65
+
66
+	// ErrorCodeMissingFrom is generated when the Dockerfile is missing
67
+	// a FROM command.
68
+	ErrorCodeMissingFrom = errcode.Register(errGroup, errcode.ErrorDescriptor{
69
+		Value:          "MISSINGFROM",
70
+		Message:        "Please provide a source image with `from` prior to run",
71
+		Description:    "The Dockerfile is missing a FROM command",
72
+		HTTPStatusCode: http.StatusInternalServerError,
73
+	})
74
+
75
+	// ErrorCodeNotOnWindows is generated when the specified Dockerfile
76
+	// command is not supported on Windows.
77
+	ErrorCodeNotOnWindows = errcode.Register(errGroup, errcode.ErrorDescriptor{
78
+		Value:          "NOTONWINDOWS",
79
+		Message:        "%s is not supported on Windows",
80
+		Description:    "The specified Dockerfile command is not supported on Windows",
81
+		HTTPStatusCode: http.StatusInternalServerError,
82
+	})
83
+
84
+	// ErrorCodeVolumeEmpty is generated when the specified Volume string
85
+	// is empty.
86
+	ErrorCodeVolumeEmpty = errcode.Register(errGroup, errcode.ErrorDescriptor{
87
+		Value:          "VOLUMEEMPTY",
88
+		Message:        "Volume specified can not be an empty string",
89
+		Description:    "The specified volume can not be an empty string",
90
+		HTTPStatusCode: http.StatusInternalServerError,
91
+	})
92
+)
0 93
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package errors
1
+
2
+// This file contains all of the errors that can be generated from the
3
+// docker/daemon component.
4
+
5
+import (
6
+	"net/http"
7
+
8
+	"github.com/docker/distribution/registry/api/errcode"
9
+)
10
+
11
+var (
12
+	// ErrorCodeNoSuchContainer is generated when we look for a container by
13
+	// name or ID and we can't find it.
14
+	ErrorCodeNoSuchContainer = errcode.Register(errGroup, errcode.ErrorDescriptor{
15
+		Value:          "NOSUCHCONTAINER",
16
+		Message:        "no such id: %s",
17
+		Description:    "The specified container can not be found",
18
+		HTTPStatusCode: http.StatusNotFound,
19
+	})
20
+)
0 21
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+package errors
1
+
2
+// This file contains all of the errors that can be generated from the
3
+// docker engine but are not tied to any specific top-level component.
4
+
5
+const errGroup = "engine"
... ...
@@ -3,6 +3,7 @@ package server
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"io"
8 9
 	"net/http"
... ...
@@ -335,7 +336,7 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
335 335
 			return err
336 336
 		}
337 337
 		sf := streamformatter.NewJSONStreamFormatter()
338
-		w.Write(sf.FormatError(err))
338
+		w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
339 339
 	}
340 340
 	return nil
341 341
 }
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/gorilla/mux"
15 15
 
16 16
 	"github.com/Sirupsen/logrus"
17
+	"github.com/docker/distribution/registry/api/errcode"
17 18
 	"github.com/docker/docker/api"
18 19
 	"github.com/docker/docker/autogen/dockerversion"
19 20
 	"github.com/docker/docker/daemon"
... ...
@@ -187,28 +188,71 @@ func httpError(w http.ResponseWriter, err error) {
187 187
 		logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
188 188
 		return
189 189
 	}
190
+
190 191
 	statusCode := http.StatusInternalServerError
191
-	// FIXME: this is brittle and should not be necessary.
192
-	// If we need to differentiate between different possible error types, we should
193
-	// create appropriate error types with clearly defined meaning.
194
-	errStr := strings.ToLower(err.Error())
195
-	for keyword, status := range map[string]int{
196
-		"not found":             http.StatusNotFound,
197
-		"no such":               http.StatusNotFound,
198
-		"bad parameter":         http.StatusBadRequest,
199
-		"conflict":              http.StatusConflict,
200
-		"impossible":            http.StatusNotAcceptable,
201
-		"wrong login/password":  http.StatusUnauthorized,
202
-		"hasn't been activated": http.StatusForbidden,
203
-	} {
204
-		if strings.Contains(errStr, keyword) {
205
-			statusCode = status
206
-			break
192
+	errMsg := err.Error()
193
+
194
+	// Based on the type of error we get we need to process things
195
+	// slightly differently to extract the error message.
196
+	// In the 'errcode.*' cases there are two different type of
197
+	// error that could be returned. errocode.ErrorCode is the base
198
+	// type of error object - it is just an 'int' that can then be
199
+	// used as the look-up key to find the message. errorcode.Error
200
+	// extends errorcode.Error by adding error-instance specific
201
+	// data, like 'details' or variable strings to be inserted into
202
+	// the message.
203
+	//
204
+	// Ideally, we should just be able to call err.Error() for all
205
+	// cases but the errcode package doesn't support that yet.
206
+	//
207
+	// Additionally, in both errcode cases, there might be an http
208
+	// status code associated with it, and if so use it.
209
+	switch err.(type) {
210
+	case errcode.ErrorCode:
211
+		daError, _ := err.(errcode.ErrorCode)
212
+		statusCode = daError.Descriptor().HTTPStatusCode
213
+		errMsg = daError.Message()
214
+
215
+	case errcode.Error:
216
+		// For reference, if you're looking for a particular error
217
+		// then you can do something like :
218
+		//   import ( derr "github.com/docker/docker/api/errors" )
219
+		//   if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
220
+
221
+		daError, _ := err.(errcode.Error)
222
+		statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
223
+		errMsg = daError.Message
224
+
225
+	default:
226
+		// This part of will be removed once we've
227
+		// converted everything over to use the errcode package
228
+
229
+		// FIXME: this is brittle and should not be necessary.
230
+		// If we need to differentiate between different possible error types,
231
+		// we should create appropriate error types with clearly defined meaning
232
+		errStr := strings.ToLower(err.Error())
233
+		for keyword, status := range map[string]int{
234
+			"not found":             http.StatusNotFound,
235
+			"no such":               http.StatusNotFound,
236
+			"bad parameter":         http.StatusBadRequest,
237
+			"conflict":              http.StatusConflict,
238
+			"impossible":            http.StatusNotAcceptable,
239
+			"wrong login/password":  http.StatusUnauthorized,
240
+			"hasn't been activated": http.StatusForbidden,
241
+		} {
242
+			if strings.Contains(errStr, keyword) {
243
+				statusCode = status
244
+				break
245
+			}
207 246
 		}
208 247
 	}
209 248
 
249
+	if statusCode == 0 {
250
+		statusCode = http.StatusInternalServerError
251
+	}
252
+
210 253
 	logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error")
211
-	http.Error(w, err.Error(), statusCode)
254
+	http.Error(w, errMsg, statusCode)
212 255
 }
213 256
 
214 257
 // writeJSON writes the value v to the http response stream as json with standard
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"strings"
19 19
 
20 20
 	"github.com/Sirupsen/logrus"
21
+	derr "github.com/docker/docker/api/errors"
21 22
 	flag "github.com/docker/docker/pkg/mflag"
22 23
 	"github.com/docker/docker/pkg/nat"
23 24
 	"github.com/docker/docker/runconfig"
... ...
@@ -41,12 +42,12 @@ func nullDispatch(b *builder, args []string, attributes map[string]bool, origina
41 41
 //
42 42
 func env(b *builder, args []string, attributes map[string]bool, original string) error {
43 43
 	if len(args) == 0 {
44
-		return fmt.Errorf("ENV requires at least one argument")
44
+		return derr.ErrorCodeAtLeastOneArg.WithArgs("ENV")
45 45
 	}
46 46
 
47 47
 	if len(args)%2 != 0 {
48 48
 		// should never get here, but just in case
49
-		return fmt.Errorf("Bad input to ENV, too many args")
49
+		return derr.ErrorCodeTooManyArgs.WithArgs("ENV")
50 50
 	}
51 51
 
52 52
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -100,7 +101,7 @@ func env(b *builder, args []string, attributes map[string]bool, original string)
100 100
 // Sets the maintainer metadata.
101 101
 func maintainer(b *builder, args []string, attributes map[string]bool, original string) error {
102 102
 	if len(args) != 1 {
103
-		return fmt.Errorf("MAINTAINER requires exactly one argument")
103
+		return derr.ErrorCodeExactlyOneArg.WithArgs("MAINTAINER")
104 104
 	}
105 105
 
106 106
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -117,11 +118,11 @@ func maintainer(b *builder, args []string, attributes map[string]bool, original
117 117
 //
118 118
 func label(b *builder, args []string, attributes map[string]bool, original string) error {
119 119
 	if len(args) == 0 {
120
-		return fmt.Errorf("LABEL requires at least one argument")
120
+		return derr.ErrorCodeAtLeastOneArg.WithArgs("LABEL")
121 121
 	}
122 122
 	if len(args)%2 != 0 {
123 123
 		// should never get here, but just in case
124
-		return fmt.Errorf("Bad input to LABEL, too many args")
124
+		return derr.ErrorCodeTooManyArgs.WithArgs("LABEL")
125 125
 	}
126 126
 
127 127
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -153,7 +154,7 @@ func label(b *builder, args []string, attributes map[string]bool, original strin
153 153
 //
154 154
 func add(b *builder, args []string, attributes map[string]bool, original string) error {
155 155
 	if len(args) < 2 {
156
-		return fmt.Errorf("ADD requires at least two arguments")
156
+		return derr.ErrorCodeAtLeastTwoArgs.WithArgs("ADD")
157 157
 	}
158 158
 
159 159
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -169,7 +170,7 @@ func add(b *builder, args []string, attributes map[string]bool, original string)
169 169
 //
170 170
 func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error {
171 171
 	if len(args) < 2 {
172
-		return fmt.Errorf("COPY requires at least two arguments")
172
+		return derr.ErrorCodeAtLeastTwoArgs.WithArgs("COPY")
173 173
 	}
174 174
 
175 175
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -185,7 +186,7 @@ func dispatchCopy(b *builder, args []string, attributes map[string]bool, origina
185 185
 //
186 186
 func from(b *builder, args []string, attributes map[string]bool, original string) error {
187 187
 	if len(args) != 1 {
188
-		return fmt.Errorf("FROM requires one argument")
188
+		return derr.ErrorCodeExactlyOneArg.WithArgs("FROM")
189 189
 	}
190 190
 
191 191
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -233,7 +234,7 @@ func from(b *builder, args []string, attributes map[string]bool, original string
233 233
 //
234 234
 func onbuild(b *builder, args []string, attributes map[string]bool, original string) error {
235 235
 	if len(args) == 0 {
236
-		return fmt.Errorf("ONBUILD requires at least one argument")
236
+		return derr.ErrorCodeAtLeastOneArg.WithArgs("ONBUILD")
237 237
 	}
238 238
 
239 239
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -243,9 +244,9 @@ func onbuild(b *builder, args []string, attributes map[string]bool, original str
243 243
 	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
244 244
 	switch triggerInstruction {
245 245
 	case "ONBUILD":
246
-		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
246
+		return derr.ErrorCodeChainOnBuild
247 247
 	case "MAINTAINER", "FROM":
248
-		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
248
+		return derr.ErrorCodeBadOnBuildCmd.WithArgs(triggerInstruction)
249 249
 	}
250 250
 
251 251
 	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
... ...
@@ -260,7 +261,7 @@ func onbuild(b *builder, args []string, attributes map[string]bool, original str
260 260
 //
261 261
 func workdir(b *builder, args []string, attributes map[string]bool, original string) error {
262 262
 	if len(args) != 1 {
263
-		return fmt.Errorf("WORKDIR requires exactly one argument")
263
+		return derr.ErrorCodeExactlyOneArg.WithArgs("WORKDIR")
264 264
 	}
265 265
 
266 266
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -317,7 +318,7 @@ func workdir(b *builder, args []string, attributes map[string]bool, original str
317 317
 //
318 318
 func run(b *builder, args []string, attributes map[string]bool, original string) error {
319 319
 	if b.image == "" && !b.noBaseImage {
320
-		return fmt.Errorf("Please provide a source image with `from` prior to run")
320
+		return derr.ErrorCodeMissingFrom
321 321
 	}
322 322
 
323 323
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -467,7 +468,7 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
467 467
 	portsTab := args
468 468
 
469 469
 	if len(args) == 0 {
470
-		return fmt.Errorf("EXPOSE requires at least one argument")
470
+		return derr.ErrorCodeAtLeastOneArg.WithArgs("EXPOSE")
471 471
 	}
472 472
 
473 473
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -506,11 +507,11 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
506 506
 //
507 507
 func user(b *builder, args []string, attributes map[string]bool, original string) error {
508 508
 	if runtime.GOOS == "windows" {
509
-		return fmt.Errorf("USER is not supported on Windows")
509
+		return derr.ErrorCodeNotOnWindows.WithArgs("USER")
510 510
 	}
511 511
 
512 512
 	if len(args) != 1 {
513
-		return fmt.Errorf("USER requires exactly one argument")
513
+		return derr.ErrorCodeExactlyOneArg.WithArgs("USER")
514 514
 	}
515 515
 
516 516
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -527,10 +528,10 @@ func user(b *builder, args []string, attributes map[string]bool, original string
527 527
 //
528 528
 func volume(b *builder, args []string, attributes map[string]bool, original string) error {
529 529
 	if runtime.GOOS == "windows" {
530
-		return fmt.Errorf("VOLUME is not supported on Windows")
530
+		return derr.ErrorCodeNotOnWindows.WithArgs("VOLUME")
531 531
 	}
532 532
 	if len(args) == 0 {
533
-		return fmt.Errorf("VOLUME requires at least one argument")
533
+		return derr.ErrorCodeAtLeastOneArg.WithArgs("VOLUME")
534 534
 	}
535 535
 
536 536
 	if err := b.BuilderFlags.Parse(); err != nil {
... ...
@@ -543,7 +544,7 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri
543 543
 	for _, v := range args {
544 544
 		v = strings.TrimSpace(v)
545 545
 		if v == "" {
546
-			return fmt.Errorf("Volume specified can not be an empty string")
546
+			return derr.ErrorCodeVolumeEmpty
547 547
 		}
548 548
 		b.Config.Volumes[v] = struct{}{}
549 549
 	}
... ...
@@ -15,6 +15,7 @@ import (
15 15
 
16 16
 	"github.com/Sirupsen/logrus"
17 17
 	"github.com/docker/docker/api"
18
+	derr "github.com/docker/docker/api/errors"
18 19
 	"github.com/docker/docker/daemon/events"
19 20
 	"github.com/docker/docker/daemon/execdriver"
20 21
 	"github.com/docker/docker/daemon/execdriver/execdrivers"
... ...
@@ -125,6 +126,10 @@ func (daemon *Daemon) Get(prefixOrName string) (*Container, error) {
125 125
 
126 126
 	containerId, indexError := daemon.idIndex.Get(prefixOrName)
127 127
 	if indexError != nil {
128
+		// When truncindex defines an error type, use that instead
129
+		if strings.Contains(indexError.Error(), "no such id") {
130
+			return nil, derr.ErrorCodeNoSuchContainer.WithArgs(prefixOrName)
131
+		}
128 132
 		return nil, indexError
129 133
 	}
130 134
 	return daemon.containers.Get(containerId), nil
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"runtime"
14 14
 	"strings"
15 15
 
16
+	"github.com/docker/distribution/registry/api/errcode"
16 17
 	"github.com/docker/docker/autogen/dockerversion"
17 18
 	"github.com/docker/docker/pkg/archive"
18 19
 	"github.com/docker/docker/pkg/fileutils"
... ...
@@ -286,3 +287,22 @@ func ImageReference(repo, ref string) string {
286 286
 func DigestReference(ref string) bool {
287 287
 	return strings.Contains(ref, ":")
288 288
 }
289
+
290
+// GetErrorMessage returns the human readable message associated with
291
+// the passed-in error. In some cases the default Error() func returns
292
+// something that is less than useful so based on its types this func
293
+// will go and get a better piece of text.
294
+func GetErrorMessage(err error) string {
295
+	switch err.(type) {
296
+	case errcode.Error:
297
+		e, _ := err.(errcode.Error)
298
+		return e.Message
299
+
300
+	case errcode.ErrorCode:
301
+		ec, _ := err.(errcode.ErrorCode)
302
+		return ec.Message()
303
+
304
+	default:
305
+		return err.Error()
306
+	}
307
+}