...

Text file src/edge-infra.dev/pkg/edge/iam/apperror/readme.md

Documentation: edge-infra.dev/pkg/edge/iam/apperror

     1
     2# How do we handle error?
     3In Go, the general approach is that every func should return an error so we can handle it immediatly.
     4That is not the case when it comes to HTTP handlers.
     5
     6```
     7func(http.ResponseWriter, *http.Request)
     8```
     9
    10That cause our code to have logs all over the handler code since each error is logged and handled.
    11
    12### Can we do better?
    13What if our HTTP handler returned an error?
    14```
    15func(http.ResponseWriter, *http.Request) error
    16```
    17
    18In that case,
    19Error handling will be delegated to a centric place, where we can do all logging too.
    20
    21
    22### How should our handler act upon error?
    23All inner calls should return an error to our handler.
    24Upon an error, our handler should decide how to act.
    25
    26We have mapped the current possible acts:
    27- abort with error
    28- abort with status
    29- abort with status JSON 
    30- redirect
    31
    32We will create a dedicated error for each possible handler act.
    33Upon error, the handler will return one of those:
    34
    35```
    36type AbortError struct {
    37	Code int
    38	Err  error
    39}
    40
    41type StatusError struct {
    42	Code int
    43	Err  error
    44}
    45
    46type JSONError struct {
    47	Code    int
    48	Message string
    49	Details map[string]interface{}
    50	Err     error
    51}
    52
    53type RedirectError struct {
    54	Code     int
    55	Location string
    56    Err      error
    57}
    58```
    59
    60A typical handler, can call other packages like a db pkg.
    61In case the other package code has an error we can decide to: 
    62- simply return the error back to the handler,
    63- wrap the error back to the handler
    64- wrap it into a custom `AppError`
    65
    66For the 3rd option,
    67We will create a custom `AppError` error that would be used by the packages that the handler call to.
    68```
    69type AppError struct {
    70	code    string
    71	message string
    72	err     error
    73}
    74```
    75
    76### Example
    77
    78```
    79func TestJSONError(t *testing.T) {
    80    // simulate a db error
    81    dbErr := errors.New("SQL_ERR::NO_ROWS")
    82
    83    // handler wraps the db error and constructs a JSONError...
    84    handlerErr := apperror.JSONError{Code: 401, Err: dbErr, Message: "user not found", Details: gin.H{"k1": "v1", "k2": 2}}
    85
    86    assert.True(t, apperror.IsJSONResponder(handlerErr))
    87    assert.Equal(t, map[string]interface{}{"k1": "v1", "k2": 2}, handlerErr.JSONDetails())
    88    assert.Equal(t, "user not found. SQL_ERR::NO_ROWS", handlerErr.Error())
    89}
    90```
    91
    92Since the handler returns a JSONError (which implements the `JSONResponder` interface),
    93Our handler wrapper will now handle ths error:
    94
    95```
    96func MakeHandlerFunc(f types.APIFunc) gin.HandlerFunc {
    97	return func(c *gin.Context) {
    98		if err := f(c); err != nil {
    99			log := log.Get(c.Request.Context())
   100            .
   101            .
   102            .
   103			if jsonErr, ok := err.(apperror.JSONResponder); ok {
   104				//msg := fmt.Sprintf("[%v] - %v", ShortOperationID(c.Request.Context()), apperror.ErrorChain(jsonErr))
   105				code, jsonObj := jsonErr.JSONResponse()
   106				msg := fmt.Sprintf("[%v] - (%d) aborting with json", ShortOperationID(c.Request.Context()), code)
   107				log.Error(jsonErr, msg, "details", jsonErr.JSONDetails())
   108
   109				c.AbortWithStatusJSON(code, jsonObj)
   110				return
   111			}
   112            .
   113            .
   114            .
   115			// handle non AppError
   116			log.Error(err, "unexpected error occurred")
   117			c.AbortWithError(500, errors.New("unexpected error occurred")) //nolint:errcheck
   118			return
   119		}
   120	}
   121}
   122
   123
   124```

View as plain text