Gin Context Error

Gin에서 에러를 request context에 저장하는 것을 제공

request별로 Error Slice를 가지고 있어, err를 append하며 저장하는 방식

/************************************/
/********* ERROR MANAGEMENT *********/
/************************************/

//Error attaches an error to the current context. The error is pushed to a list of errors.
// It's a good idea to callError for each error that occurred during the resolution of a request.
// A middleware can be used to collect all the errors and push them to a database together,
// print a log, or append it in the HTTP response.
//Error will panic if err is nil.
func (c *Context) Error(err error) *Error {
	if err == nil {
		panic("err is nil")
	}

	var parsedError *Error
	ok := errors.As(err, &parsedError)
	if !ok {
		parsedError = &Error{
			Err:  err,
			Type: ErrorTypePrivate,
		}
	}

	c.Errors = append(c.Errors, parsedError)
	return parsedError
}

Error 저장 및 메시지

api의 함수에서 ctx.Error(err) 를 사용하여 err를 저장

func GetUser(ctx *gin.Context) {

	userDoc, err := service.GetUser(...)
	if err != nil {
		ctx.Error(err)
		return
	}
	ctx.JSON(http.StatusOk, userDoc)
}

service나 다른 레이어에서도 ctx를 넘겨 계속 쌓을 수도 있을 것 같음.

이렇게 쌓인 error의 메시지(err.Error())를 ctx.Errors.Errors() 를 활용하면 한번에 얻을 수 있다.

// Errors returns an array with all the error messages.
// Example:
// 		c.Error(errors.New("first"))
// 		c.Error(errors.New("second"))
// 		c.Error(errors.New("third"))
// 		c.Errors.Errors() // == []string{"first", "second", "third"}
func (a errorMsgs) Errors() []string {
	if len(a) == 0 {
		return nil
	}
	errorStrings := make([]string, len(a))
	for i, err := range a {
		errorStrings[i] = err.Error()
	}
	return errorStrings
}

하지만 error를 wrapping하거나 한다면, 위의 함수는 중복된 메시지들을 출력하여 실효성은 없을 것 같음.

Error 처리 분리 가능

Middleware에서 저장된 error를 꺼내서 활용 가능

func ErrorHandler(ctx *gin.Context) {
	funcName := "ErrorHandler"
	ctx.Next()

	// 이미 status가 쓰였다면 종료
	if ctx.Writer.Written() {
		return
	}

	errs := ctx.Errors

	for _, err := range errs {
		unwrapped := errors.Unwrap(err)
		switch  {
		case customErrorType.IsXXXErr(unwrapped): // your custom error check func
			log.Warn(err.Error())
			ctx.JSON(http.StatusNotFound, /*your response object*/)
			return
		default:
			log.Error(err.Error())
			ctx.JSON(http.StatusInternalServerError, /*your response object*/)
			return
		}
	}

	// or if err is added just in controller layer,
	err := errs.Last()
	unwrapped := errors.Unwrap(err)
	switch  {
	case customErrorType.IsXXXErr(unwrapped): // your custom error check func
		log.Warn(err.Error())
		ctx.JSON(http.StatusNotFound, /*your response object*/)
		return
	default:
		log.Error(err.Error())
		ctx.JSON(http.StatusInternalServerError, /*your response object*/)
		return
	}
}

위와 같은 방법은 에러 처리가 분리되었다 라는 점 빼고는 장점이 없어보인다고 생각

error를 wrapping을 한다면, 로직 상 error를 판단할 수 있는 곳에서 log를 찍고, api 코드에서 직접 response를 하는게 좋아보임.