Go + gin でエラーハンドリング
概要
前回 レスポンスモデルを作成しました
今回はエラーハンドラを作成します
ハンドラや各種関数では Error を返却しそれをまとめてハンドリングするミドルウェアをアプリに登録します
環境
macOS 26.5.1
golang 1.26.4
カスタムエラーの作成
カスタムエラーを作成します
エラーは発生した箇所は基本的にこのエラーは return するようにします
package error
type AppError struct {
Code string
Message string
Status int
}
func ( e * AppError) Error ( ) string {
return e. Message
}
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 {
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)
}
}
}
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 を使わずにエラーを返すようにします
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 するだけです