├── doc.go ├── go.mod ├── apiserver.go ├── code.go ├── README.md ├── sample_code_generated.go ├── base.go ├── error_code_generated.md ├── examples └── main.go └── go.sum /doc.go: -------------------------------------------------------------------------------- 1 | package code // import "github.com/marmotedu/sample-code" 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/marmotedu/sample-code 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/kr/text v0.2.0 // indirect 7 | github.com/marmotedu/errors v1.0.2 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 9 | github.com/novalagung/gubrak v1.0.0 10 | github.com/stretchr/testify v1.6.1 // indirect 11 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /apiserver.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | // iam-apiserver服务:用户类错误 4 | const ( 5 | // ErrUserNotFound - 404: User not found. 6 | ErrUserNotFound int = iota + 110001 7 | 8 | // ErrUserAlreadyExist - 400: User already exist. 9 | ErrUserAlreadyExist 10 | ) 11 | 12 | // iam-apiserver服务:密钥类错误 13 | const ( 14 | // ErrEncrypt - 400: Secret reach the max count. 15 | ErrReachMaxCount int = iota + 110101 16 | 17 | // ErrSecretNotFound - 404: Secret not found. 18 | ErrSecretNotFound 19 | ) 20 | -------------------------------------------------------------------------------- /code.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "github.com/marmotedu/errors" 5 | "github.com/novalagung/gubrak" 6 | ) 7 | 8 | // ErrCode implements `github.com/marmotedu/errors`.Coder interface. 9 | type ErrCode struct { 10 | // C refers to the code of the ErrCode. 11 | C int 12 | 13 | // HTTP status that should be used for the associated error code. 14 | HTTP int 15 | 16 | // External (user) facing error text. 17 | Ext string 18 | 19 | // Ref specify the reference document. 20 | Ref string 21 | } 22 | 23 | // Code returns the integer code of ErrCode. 24 | func (coder ErrCode) Code() int { 25 | return coder.C 26 | } 27 | 28 | // String implements stringer. String returns the external error message, 29 | // if any. 30 | func (coder ErrCode) String() string { 31 | return coder.Ext 32 | } 33 | 34 | // Reference returns the reference document. 35 | func (coder ErrCode) Reference() string { 36 | return coder.Ref 37 | } 38 | 39 | // HTTPStatus returns the associated HTTP status code, if any. Otherwise, 40 | // returns 200. 41 | func (coder ErrCode) HTTPStatus() int { 42 | if coder.HTTP == 0 { 43 | return 500 44 | } 45 | return coder.HTTP 46 | } 47 | 48 | func register(code int, httpStatus int, message string, refs ...string) { 49 | found, _ := gubrak.Includes([]int{200, 400, 401, 403, 404, 500}, httpStatus) 50 | if !found { 51 | panic("http code not in `200, 400, 401, 403, 404, 500`") 52 | } 53 | 54 | var reference string 55 | if len(refs) > 0 { 56 | reference = refs[0] 57 | } 58 | 59 | coder := &ErrCode{ 60 | C: code, 61 | HTTP: httpStatus, 62 | Ext: message, 63 | Ref: reference, 64 | } 65 | 66 | errors.MustRegister(coder) 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 适配于 `github.com/marmotedu/errors` 错误包的错误码实现。 2 | 3 | ## Code 设计规范 4 | 5 | Code 代码从 100101 开始,1000 以下为 `github.com/marmotedu/errors` 保留 code. 6 | 7 | 错误代码说明:100101 8 | + 10: 服务 9 | + 01: 模块 10 | + 01: 模块下的错误码序号,每个模块可以注册 100 个错误 11 | 12 | ### 服务和模块说明 13 | 14 | |服务|模块|说明| 15 | |----|----|----| 16 | |10|00|通用 - 基本错误| 17 | |10|01|通用 - 数据库类错误| 18 | |10|02|通用 - 认证授权类错误| 19 | |10|03|通用 - 加解码类错误| 20 | |11|00|iam-apiserver服务 - 用户相关(模块)错误| 21 | |11|01|iam-apiserver服务 - 密钥相关(模块)错误| 22 | 23 | > **通用** - 所有服务都适用的错误,提高复用性,避免重复造轮子 24 | 25 | ## 错误描述规范 26 | 27 | 错误描述包括:对外的错误描述和对内的错误描述两部分。 28 | 29 | ### 对外的错误描述 30 | 31 | - 对外暴露的错误,统一大写开头,结尾不要加`.` 32 | - 对外暴露的错误,要简洁,并能准确说明问题 33 | - 对外暴露的错误说明,应该是 `该怎么做` 而不是 `哪里错了` 34 | 35 | ### 对内的错误描述 36 | 37 | - 告诉用户他们可以做什么,而不是告诉他们不能做什么。 38 | - 当声明一个需求时,用 must 而不是 should。例如,must be greater than 0、must match regex '[a-z]+'。 39 | - 当声明一个格式不对时,用 must not。例如,must not contain。 40 | - 当声明一个动作时用 may not。例如,may not be specified when otherField is empty、only name may be specified。 41 | - 引用文字字符串值时,请在单引号中指示文字。例如,ust not contain '..'。 42 | - 当引用另一个字段名称时,请在反引号中指定该名称。例如,must be greater than request。 43 | - 指定不等时,请使用单词而不是符号。例如,must be less than 256、must be greater than or equal to 0 (不要用 larger than、bigger than、more than、higher than)。 44 | - 指定数字范围时,请尽可能使用包含范围。 45 | - 建议 Go 1.13 以上,error 生成方式为 fmt.Errorf("module xxx: %w", err)。 46 | - 错误描述用小写字母开头,结尾不要加标点符号。 47 | 48 | > 错误信息是直接暴露给用户的,不能包含敏感信息 49 | 50 | ## 错误记录规范 51 | 52 | 在错误产生的最原始位置调用日志,打印错误信息,其它位置直接返回。 53 | 54 | 当错误发生时,调用log包打印错误,通过log包的caller功能,可以定位到log语句的位置,也即能够定位到错误发生的位置。当使用这种方式来打印日志时,需要中遵循以下规范: 55 | 56 | - 只在错误产生的最初位置打印日志,其它地方直接返回错误,不需要再对错误进行封装。 57 | - 当代码调用第三方包的函数时,第三方包函数出错时,打印错误信息。比如: 58 | 59 | ```go 60 | if err := os.Chdir("/root"); err != nil { 61 | log.Errorf("change dir failed: %v", err) 62 | } 63 | ``` 64 | 65 | ## 错误码 66 | 67 | 具体错误码,请参考:[错误码](./error_code_generated.md) 68 | -------------------------------------------------------------------------------- /sample_code_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Lingfei Kong . All rights reserved. 2 | // Use of this source code is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Code generated by "codegen -type=int"; DO NOT EDIT. 6 | 7 | package code 8 | 9 | // init register error codes defines in this source code to `github.com/marmotedu/errors` 10 | func init() { 11 | register(ErrUserNotFound, 404, "User not found") 12 | register(ErrUserAlreadyExist, 400, "User already exist") 13 | register(ErrReachMaxCount, 400, "Secret reach the max count") 14 | register(ErrSecretNotFound, 404, "Secret not found") 15 | register(ErrSuccess, 200, "OK") 16 | register(ErrUnknown, 500, "Internal server error") 17 | register(ErrBind, 400, "Error occurred while binding the request body to the struct") 18 | register(ErrValidation, 400, "Validation failed") 19 | register(ErrTokenInvalid, 401, "Token invalid") 20 | register(ErrDatabase, 500, "Database error") 21 | register(ErrEncrypt, 401, "Error occurred while encrypting the user password") 22 | register(ErrSignatureInvalid, 401, "Signature is invalid") 23 | register(ErrExpired, 401, "Token expired") 24 | register(ErrInvalidAuthHeader, 401, "Invalid authorization header") 25 | register(ErrMissingHeader, 401, "The `Authorization` header was empty") 26 | register(ErrorExpired, 401, "Token expired") 27 | register(ErrPasswordIncorrect, 401, "Password was incorrect") 28 | register(ErrPermissionDenied, 403, "Permission denied") 29 | register(ErrEncodingFailed, 500, "Encoding failed due to an error with the data") 30 | register(ErrDecodingFailed, 500, "Decoding failed due to an error with the data") 31 | register(ErrInvalidJSON, 500, "Data is not valid JSON") 32 | register(ErrEncodingJSON, 500, "JSON data could not be encoded") 33 | register(ErrDecodingJSON, 500, "JSON data could not be decoded") 34 | register(ErrInvalidYaml, 500, "Data is not valid Yaml") 35 | register(ErrEncodingYaml, 500, "Yaml data could not be encoded") 36 | register(ErrDecodingYaml, 500, "Yaml data could not be decoded") 37 | } 38 | -------------------------------------------------------------------------------- /base.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | //go:generate codegen -type=int 4 | //go:generate codegen -type=int -doc -output ./error_code_generated.md 5 | 6 | // 通用: 基本错误 7 | // Code must start with 1xxxxx 8 | const ( 9 | // ErrSuccess - 200: OK. 10 | ErrSuccess int = iota + 100001 11 | 12 | // ErrUnknown - 500: Internal server error. 13 | ErrUnknown 14 | 15 | // ErrBind - 400: Error occurred while binding the request body to the struct. 16 | ErrBind 17 | 18 | // ErrValidation - 400: Validation failed. 19 | ErrValidation 20 | 21 | // ErrTokenInvalid - 401: Token invalid. 22 | ErrTokenInvalid 23 | ) 24 | 25 | // 通用:数据库类错误 26 | const ( 27 | // ErrDatabase - 500: Database error. 28 | ErrDatabase int = iota + 100101 29 | ) 30 | 31 | // 通用:认证授权类错误 32 | const ( 33 | // ErrEncrypt - 401: Error occurred while encrypting the user password. 34 | ErrEncrypt int = iota + 100201 35 | 36 | // ErrSignatureInvalid - 401: Signature is invalid. 37 | ErrSignatureInvalid 38 | 39 | // ErrExpired - 401: Token expired. 40 | ErrExpired 41 | 42 | // ErrInvalidAuthHeader - 401: Invalid authorization header. 43 | ErrInvalidAuthHeader 44 | 45 | // ErrMissingHeader - 401: The `Authorization` header was empty. 46 | ErrMissingHeader 47 | 48 | // ErrorExpired - 401: Token expired. 49 | ErrorExpired 50 | 51 | // ErrPasswordIncorrect - 401: Password was incorrect. 52 | ErrPasswordIncorrect 53 | 54 | // PermissionDenied - 403: Permission denied. 55 | ErrPermissionDenied 56 | ) 57 | 58 | // 通用:编解码类错误 59 | const ( 60 | // ErrEncodingFailed - 500: Encoding failed due to an error with the data. 61 | ErrEncodingFailed int = iota + 100301 62 | 63 | // ErrDecodingFailed - 500: Decoding failed due to an error with the data. 64 | ErrDecodingFailed 65 | 66 | // ErrInvalidJSON - 500: Data is not valid JSON. 67 | ErrInvalidJSON 68 | 69 | // ErrEncodingJSON - 500: JSON data could not be encoded. 70 | ErrEncodingJSON 71 | 72 | // ErrDecodingJSON - 500: JSON data could not be decoded. 73 | ErrDecodingJSON 74 | 75 | // ErrInvalidYaml - 500: Data is not valid Yaml. 76 | ErrInvalidYaml 77 | 78 | // ErrEncodingYaml - 500: Yaml data could not be encoded. 79 | ErrEncodingYaml 80 | 81 | // ErrDecodingYaml - 500: Yaml data could not be decoded. 82 | ErrDecodingYaml 83 | ) 84 | -------------------------------------------------------------------------------- /error_code_generated.md: -------------------------------------------------------------------------------- 1 | # 错误码 2 | 3 | !!IAM 系统错误码列表,由 `codegen -type=int -doc` 命令生成,不要对此文件做任何更改。 4 | 5 | ## 功能说明 6 | 7 | 如果返回结果中存在 `code` 字段,则表示调用 API 接口失败。例如: 8 | 9 | ```json 10 | { 11 | "code": 100101, 12 | "message": "Database error" 13 | } 14 | ``` 15 | 16 | 上述返回中 `code` 表示错误码,`message` 表示该错误的具体信息。每个错误同时也对应一个 HTTP 状态码,比如上述错误码对应了 HTTP 状态码 500(Internal Server Error)。 17 | 18 | ## 错误码列表 19 | 20 | IAM 系统支持的错误码列表如下: 21 | 22 | | Identifier | Code | HTTP Code | Description | 23 | | ---------- | ---- | --------- | ----------- | 24 | | ErrUserNotFound | 110001 | 404 | User not found | 25 | | ErrUserAlreadyExist | 110002 | 400 | User already exist | 26 | | ErrReachMaxCount | 110101 | 400 | Secret reach the max count | 27 | | ErrSecretNotFound | 110102 | 404 | Secret not found | 28 | | ErrSuccess | 100001 | 200 | OK | 29 | | ErrUnknown | 100002 | 500 | Internal server error | 30 | | ErrBind | 100003 | 400 | Error occurred while binding the request body to the struct | 31 | | ErrValidation | 100004 | 400 | Validation failed | 32 | | ErrTokenInvalid | 100005 | 401 | Token invalid | 33 | | ErrDatabase | 100101 | 500 | Database error | 34 | | ErrEncrypt | 100201 | 401 | Error occurred while encrypting the user password | 35 | | ErrSignatureInvalid | 100202 | 401 | Signature is invalid | 36 | | ErrExpired | 100203 | 401 | Token expired | 37 | | ErrInvalidAuthHeader | 100204 | 401 | Invalid authorization header | 38 | | ErrMissingHeader | 100205 | 401 | The `Authorization` header was empty | 39 | | ErrorExpired | 100206 | 401 | Token expired | 40 | | ErrPasswordIncorrect | 100207 | 401 | Password was incorrect | 41 | | ErrPermissionDenied | 100208 | 403 | Permission denied | 42 | | ErrEncodingFailed | 100301 | 500 | Encoding failed due to an error with the data | 43 | | ErrDecodingFailed | 100302 | 500 | Decoding failed due to an error with the data | 44 | | ErrInvalidJSON | 100303 | 500 | Data is not valid JSON | 45 | | ErrEncodingJSON | 100304 | 500 | JSON data could not be encoded | 46 | | ErrDecodingJSON | 100305 | 500 | JSON data could not be decoded | 47 | | ErrInvalidYaml | 100306 | 500 | Data is not valid Yaml | 48 | | ErrEncodingYaml | 100307 | 500 | Yaml data could not be encoded | 49 | | ErrDecodingYaml | 100308 | 500 | Yaml data could not be decoded | 50 | 51 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/marmotedu/errors" 7 | 8 | code "github.com/marmotedu/sample-code" 9 | ) 10 | 11 | func main() { 12 | if err := bindUser(); err != nil { 13 | // %s: Returns the user-safe error string mapped to the error code or the error message if none is specified. 14 | fmt.Println("====================> %s <====================") 15 | fmt.Printf("%s\n\n", err) 16 | 17 | // %v: Alias for %s. 18 | fmt.Println("====================> %v <====================") 19 | fmt.Printf("%v\n\n", err) 20 | 21 | // %-v: Output caller details, useful for troubleshooting. 22 | fmt.Println("====================> %-v <====================") 23 | fmt.Printf("%-v\n\n", err) 24 | 25 | // %+v: Output full error stack details, useful for debugging. 26 | fmt.Println("====================> %+v <====================") 27 | fmt.Printf("%+v\n\n", err) 28 | 29 | // %#-v: Output caller details, useful for troubleshooting with JSON formatted output. 30 | fmt.Println("====================> %#-v <====================") 31 | fmt.Printf("%#-v\n\n", err) 32 | 33 | // %#+v: Output full error stack details, useful for debugging with JSON formatted output. 34 | fmt.Println("====================> %#+v <====================") 35 | fmt.Printf("%#+v\n\n", err) 36 | 37 | // do some business process based on the error type 38 | if errors.IsCode(err, code.ErrEncodingFailed) { 39 | fmt.Println("this is a ErrEncodingFailed error") 40 | } 41 | 42 | if errors.IsCode(err, code.ErrDatabase) { 43 | fmt.Println("this is a ErrDatabase error") 44 | } 45 | 46 | // we can also find the cause error 47 | fmt.Println(errors.Cause(err)) 48 | } 49 | } 50 | 51 | func bindUser() error { 52 | if err := getUser(); err != nil { 53 | // Step3: Wrap the error with a new error message and a new error code if needed. 54 | return errors.WrapC(err, code.ErrEncodingFailed, "encoding user 'Lingfei Kong' failed.") 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func getUser() error { 61 | if err := queryDatabase(); err != nil { 62 | // Step2: Wrap the error with a new error message. 63 | return errors.Wrap(err, "get user failed.") 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func queryDatabase() error { 70 | // Step1. Create error with specified error code. 71 | return errors.WithCode(code.ErrDatabase, "user 'Lingfei Kong' not found.") 72 | } 73 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DefinitelyMod/gocsv v0.0.0-20181205141819-acfa5f112b45 h1:+OD9vawobD89HK04zwMokunBCSEeAb08VWAHPUMg+UE= 2 | github.com/DefinitelyMod/gocsv v0.0.0-20181205141819-acfa5f112b45/go.mod h1:+nlrAh0au59iC1KN5RA1h1NdiOQYlNOBrbtE1Plqht4= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 8 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 9 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 10 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 11 | github.com/marmotedu/errors v1.0.2 h1:qx9GtOljmAL+wLuemahe3WSWdXyEpJvLBlpXK8y2rdI= 12 | github.com/marmotedu/errors v1.0.2/go.mod h1:xNqbJJRD50/RGSjbfqF01CTLegWK+gtRgeJ6ExVzQQ8= 13 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 14 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 15 | github.com/novalagung/gubrak v1.0.0 h1:+iDvzUcSHUoa3bwP/ig40K2h9X+5cX2w5qcBb3izAwo= 16 | github.com/novalagung/gubrak v1.0.0/go.mod h1:lahTbjdK/OLI9Y4alRlf003XEwbiOj7ERkmDHFFbzLk= 17 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 18 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 23 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 24 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 27 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 29 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 30 | --------------------------------------------------------------------------------