125 lines
3.2 KiB
Markdown
125 lines
3.2 KiB
Markdown
## Purpose of errs package
|
|
1. compatible with `error` interface
|
|
2. encapsulate error message with functions in `easy_function.go`
|
|
3. easy for gRPC client/server
|
|
4. support err's chain by [Working with Errors in Go 1.13](https://blog.golang.org/go1.13-errors)
|
|
|
|
|
|
## Example - Normal function
|
|
Using builtin functions `InvalidInput` to generate `Err struct`
|
|
|
|
**please add your own functions if not exist**
|
|
|
|
```go
|
|
package main
|
|
|
|
import "adc.github.trendmicro.com/commercial-mgcp/library-go/pkg/errs"
|
|
|
|
func handleParam(s string) error {
|
|
// check user_id format
|
|
if ok := userIDFormat(s); !ok {
|
|
return errs.InvalidFormat("param user_id")
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
## Example - gRPC Server
|
|
`GetAgent` is a method of gRPC server, it wraps `Err` struct to `status.Status` struct
|
|
```go
|
|
func (as *agentService) GetAgent(ctx context.Context, req *cloudep.GetAgentRequest) (*cloudep.GetAgentResponse, error) {
|
|
l := log.WithFields(logger.Fields{"tenant_id": req.TenantId, "agent_id": req.AgentId, "method": "GetAgent"})
|
|
|
|
tenantID, err := primitive.ObjectIDFromHex(req.TenantId)
|
|
if err != nil {
|
|
// err maybe errs.Err or general error
|
|
// it's safe to use Convert() here
|
|
return nil, status.Convert(err).Err()
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
|
|
## Example - gRPC Client
|
|
Calling `GetAgent` and retry when Category is "DB"
|
|
```go
|
|
client := cloudep.NewAgentServiceClient(conn)
|
|
req := cloudep.GetAgentRequest{
|
|
TenantId: "not-a-valid-object-id",
|
|
AgentId: "5eb4fa99006d53c0cb6f9cfe",
|
|
}
|
|
|
|
// Retry if DB error
|
|
for retry := 3; retry > 0 ; retry-- {
|
|
resp, err := client.GetAgent(context.Background(), &req)
|
|
if err != nil {
|
|
e := errs.FromGRPCError(err)
|
|
if e.Category() == code.CatGRPC {
|
|
if e.Code() == uint32(codes.Unavailable) {
|
|
log.warn("GRPC service unavailable. Retrying...")
|
|
continue
|
|
}
|
|
log.errorf("GRPC built-in error: %v", e)
|
|
}
|
|
if e.Category() == code.CatDB {
|
|
log.warn("retry...")
|
|
continue
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
```
|
|
|
|
## Example - REST server
|
|
1. handling gRPC client error
|
|
2. transfer to HTTP code
|
|
3. transfer to Error body
|
|
|
|
```go
|
|
func Handler(c *gin.Context) {
|
|
|
|
// handle error from gRPC client
|
|
resp, err := client.GetAgent(context.Background(), &req)
|
|
if err != nil {
|
|
// to Err
|
|
e := errs.FromGRPCError(err)
|
|
|
|
// get HTTP code & response struct
|
|
// 2nd parameter true means return general error message to user
|
|
c.JSON(e.HTTPStatus(), general.NewError(e, true))
|
|
}
|
|
|
|
}
|
|
```
|
|
|
|
## Example - Error Chain
|
|
1. set internal error by func `Wrap`
|
|
2. check Err has any error in err's chain matches the target by `errors.Is`
|
|
3. finds the first error in err's chain that matches target by `errors.As`
|
|
|
|
```go
|
|
// define a specific err type
|
|
type testErr struct {
|
|
code int
|
|
}
|
|
|
|
func (e *testErr) Error() string {
|
|
return strconv.Itoa(e.code)
|
|
}
|
|
|
|
func main() {
|
|
layer1Err := &testErr{code: 123}
|
|
// error chain: InvalidFormat -> layer 1 err
|
|
layer2Err := InvalidFormat("field A", "")
|
|
layer2Err.Wrap(layer1Err) //set internal error
|
|
|
|
// errors.Is should report true
|
|
hasLayer1Err := errors.Is(layer2Err, layer1Err)
|
|
|
|
// errors.As should return internal error
|
|
var internalErr *testErr
|
|
ok := errors.As(layer2Err, &internalErr)
|
|
}
|
|
``` |