概要
前回レスポンスモデルを作成しました
今回はエラーハンドラを作成します
ハンドラや各種関数では Error を返却しそれをまとめてハンドリングするミドルウェアをアプリに登録します
環境
- macOS 26.5.1
- golang 1.26.4
- gin 1.12.0
カスタムエラーの作成
カスタムエラーを作成します
エラーは発生した箇所は基本的にこのエラーは return するようにします
- vim error/error.go
package error
type AppError struct {
Code string
Message string
Status int
}
func (e *AppError) Error() string {
return e.Message
}
// helper
func New(code, message string, status int) *AppError {
return &AppError{
Code: code,
Message: message,
Status: status,
}
}
エラーハンドラの作成
各所では c.JSON を使ってエラーを返却せずに return Error します
そしてそこで返されたエラーをこのハンドラがキャッチしここでエラーレスポンスを使って返却します
今回はカスタムエラー以外は 500 エラーにしています
- vim middleware/error_handler.go
package middleware
import (
"net/http"
"gin-sample/error"
"gin-sample/model"
"github.com/gin-gonic/gin"
)
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
// panic捕捉
model.RespondError(c, http.StatusInternalServerError,
"INTERNAL_ERROR", "unexpected error")
}
}()
c.Next()
// エラーがなければ何もしない
if len(c.Errors) == 0 {
return
}
err := c.Errors.Last().Err
switch e := err.(type) {
case *error.AppError:
model.RespondError(c, e.Status, e.Code, e.Message)
default:
model.RespondError(c, http.StatusInternalServerError,
"INTERNAL_ERROR", err.Error())
}
}
}
アクションハンドラの修正
アクションハンドラ側でエラーを生成した場合はここでも return するようにします
少しポイントが必要で gin に登録するハンドラメソッドの形式にするために Wrap 関数を作成してあげます
package handler
import (
customError "gin-sample/error"
"gin-sample/model"
"github.com/gin-gonic/gin"
)
type AppHandler func(c *gin.Context) error
func Wrap(h AppHandler) gin.HandlerFunc {
return func(c *gin.Context) {
if err := h(c); err != nil {
c.Error(err) // middlewareに流す
}
}
}
func HandleAction(dispatcher *Dispatcher) AppHandler {
return func(c *gin.Context) error {
requestID := c.GetHeader("X-Request-Id")
if requestID == "" {
requestID = "dummy-request-id"
}
// パラメータ統合
if err := c.Request.ParseForm(); err != nil {
return customError.New("INVALID_REQUEST", "invalid request", 400)
}
params := make(map[string]string)
for key, values := range c.Request.Form {
if len(values) > 0 {
params[key] = values[0]
}
}
actionName := params["Action"]
result, err := dispatcher.Execute(actionName, params, c.Request.Method, c.Request)
if err != nil {
return err // ★ここ重要
}
// 成功時だけレスポンスを書く
model.RespondSuccess(c, gin.H{
"requestId": requestID,
"action": actionName,
"result": result,
})
return nil
}
}
ハンドラ関数でもエラーを返すようにする
ハンドラ関数でも c.JSON を使わずにエラーを返すようにします
- vim handler/action.go
package handler
import (
"net/http"
"strconv"
customError "gin-sample/error"
)
func HelloAction(params map[string]string, method string, r *http.Request) (any, error) {
name := params["name"]
if name == "" {
name = "guest"
}
return map[string]string{
"message": "hello " + name,
}, nil
}
func SumAction(params map[string]string, method string, r *http.Request) (any, error) {
aStr := params["a"]
bStr := params["b"]
a, err := strconv.Atoi(aStr)
if err != nil {
return nil, customError.New("INVALID_PARAM", "a is invalid", 400)
}
b, err := strconv.Atoi(bStr)
if err != nil {
return nil, customError.New("INVALID_PARAM", "b is invalid", 400)
}
sum := a + b
return map[string]interface{}{
"a": a,
"b": b,
"sum": sum,
}, nil
}
動作確認
- gofmt -w .
- go run main.go
curl -X POST http://localhost:8080 \
-H "Content-Type: multipart/form-data; boundary=----abc"
{"success":false,"error":{"code":"INTERNAL_ERROR","message":"unknown action: "}}
curl -X POST http://localhost:8080 \
-d "Action=Sum&"
{"success":false,"error":{"code":"INVALID_PARAM","message":"a is invalid"}}
c.Request.ParseForm() の部分はエラーにするのは難しいです
最後に
無闇矢鱈に c.JSON でレスポンスは返却せずにエラーハンドラでまとめましょう
その他の箇所では単純に error を return するだけです
0 件のコメント:
コメントを投稿