library-go/errs
daniel.w 4857756e2f add error scope order 2024-10-06 15:55:18 +08:00
..
code add error scope order 2024-10-06 15:55:18 +08:00
easy_func.go feature/errv2 (#7) 2024-08-27 13:54:29 +00:00
easy_func_test.go feature/errv2 (#7) 2024-08-27 13:54:29 +00:00
errors.go feature/errv2 (#7) 2024-08-27 13:54:29 +00:00
errors_test.go feature/errv2 (#7) 2024-08-27 13:54:29 +00:00
go.mod feature/errv2 (#7) 2024-08-27 13:54:29 +00:00
readme.md feature/errv2 (#7) 2024-08-27 13:54:29 +00:00

readme.md

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

Example - Normal function

Using builtin functions InvalidInput to generate Err struct

please add your own functions if not exist

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

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"

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
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
// 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)
}