## 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) } ```