Skip to content

Commit

Permalink
Merge pull request #7 from naohito-T/feature/error-handler
Browse files Browse the repository at this point in the history
Feature/error handler
  • Loading branch information
naohito-T committed Apr 21, 2024
2 parents 1531181 + b909b7a commit 6cebb61
Show file tree
Hide file tree
Showing 22 changed files with 205 additions and 68 deletions.
8 changes: 0 additions & 8 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
"editor.formatOnSave": true,
"go.formatTool": "goimports",
"go.lintTool": "golangci-lint",
// workspaceFolderはVisual Studio Code (VS Code)の変数で、現在開いているワークスペースのルートフォルダを指します。
// つまり、この変数はVS Codeで開かれているプロジェクトの最上位ディレクトリのパスに解決されます。
"go.lintFlags": [
"--config=${workspaceFolder}/backend/.golangci.yml",
"--fast"
Expand All @@ -14,12 +12,6 @@
"source.organizeImports": "explicit"
}
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"markdownlint.config": {
"MD013": false,
"MD033": false
Expand Down
2 changes: 1 addition & 1 deletion backend/.go-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.21.1
1.21.0
6 changes: 6 additions & 0 deletions backend/.golangci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# see: https://golangci-lint.run/
# reference: https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
run:
deadline: 5m

issues:
# golangci-lintがdefaultで無効にしているlintersを有効にする
exclude-use-default: false
# linters-settings:
linters:
disable-all: true
enable:
Expand Down
4 changes: 2 additions & 2 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ test:
# see https://logmi.jp/tech/articles/324549
# セキュリティチェック
sec:
gosec ./...
golangci-lint run --enable=gosec ./...

# エラーチェック
errcheck:
errcheck ./...

# 静的解析
staticcheck:
staticcheck ./...
golangci-lint run --enable=staticcheck ./...

# フォーマットとインポートの整理
format:
Expand Down
Empty file removed backend/build/.keep
Empty file.
14 changes: 14 additions & 0 deletions backend/configs/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package configs

// type MyType intと宣言するDefined type
// 以前はNamed typeと言っていたが、Go1.11からDefined typeと呼ぶようになった
// type MyType = intと宣言するType alias
// see: https://zenn.dev/bluetree/articles/9d52842dff35cc
// go 1.19から導入された型エイリアス(これで推測され、string()などのキャストが不要になる)
type path = string

const (
Health path = "/health"

Check warning on line 11 in backend/configs/router.go

View workflow job for this annotation

GitHub Actions / lint-backend

exported: exported const Health should have comment (or a comment on this block) or be unexported (revive)
GetShortURL path = "/api/v1/urls/:shortUrl"
CreateShortURL path = "/api/v1/urls"
)
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions backend/docker/scripts/tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

# 将来的には開発ツールもdocker内で実行するようにする。
docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.57.2 golangci-lint run -v
29 changes: 29 additions & 0 deletions backend/domain/customerror/code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package customerror

// ApplicationError はアプリケーション特有のエラー情報を保持する基本構造体です。
type ApplicationErrorCode struct {
// StatusCode(JSON parseでは構造体省略)
Status int `json:"-"`
// Code はエラーコードを表します。
Code string `json:"code"`
// Message はエラーメッセージを表します。
Message string `json:"message"`
}

var (
WrongEmailVerificationCode ApplicationErrorCode = ApplicationErrorCode{
Code: "WRONG_EMAIL_VERIFICATION_ERROR",
Message: "Wrong email verification code",
}

// Unknown Error: システムがエラーの原因を特定できないか、エラーの種類が分類されていない場合に使用する。
UnknownCode ApplicationErrorCode = ApplicationErrorCode{
Code: "unknown_error",
Message: "unknown_error",
}

UnexpectedCode ApplicationErrorCode = ApplicationErrorCode{
Code: "unexpected_error",
Message: "unexpected_error",
}
)
59 changes: 59 additions & 0 deletions backend/domain/customerror/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package customerror

import (
"fmt"
"net/http"
"net/url"
)

// ApplicationError はアプリケーション特有のエラー情報を保持する基本構造体です。
type ApplicationError struct {
// StatusCode(JSON parseでは構造体省略)
Status int `json:"-"`
// Code はエラーコードを表します。
Code string `json:"code"`
// Message はエラーメッセージを表します。
Message string `json:"message"`
}

// Error メソッドは error インターフェースを実装します。
func (e *ApplicationError) Error() string {
return fmt.Sprintf("Code: %s, Message: %s", e.Code, e.Message)
}

// WrongEmailVerificationError は特定の条件下で利用されるカスタムエラーです。
type WrongEmailVerificationError struct {
ApplicationError
IsTally bool
IsRedirect bool
RedirectQuery *url.Values
}

// NewWrongEmailVerificationError は WrongEmailVerificationError を生成します。
func NewWrongEmailVerificationError(isTally, isRedirect bool, redirectQuery string) *WrongEmailVerificationError {
q, _ := url.ParseQuery(redirectQuery)
return &WrongEmailVerificationError{
ApplicationError: ApplicationError{
Status: http.StatusBadRequest,
Code: "WRONG_EMAIL_VERIFICATION_ERROR",
Message: "Wrong email verification code",
},
IsTally: isTally,
IsRedirect: isRedirect,
RedirectQuery: &q,
}
}

// LogError はエラーのロギングを行うメソッドです。
func (e *WrongEmailVerificationError) LogError(req *http.Request) {
fmt.Println("Error occurred:", e.Error(), "Query:", e.RedirectQuery.Encode())
}

// Is メソッドを追加
// func (e *WrongEmailVerificationError) Is(target error) bool {
// t, ok := target.(*WrongEmailVerificationError)
// return ok && e.Code == t.Code
// }

// WrongEmailVerificationErrorInstance は WrongEmailVerificationError のグローバルインスタンスです。
var WrongEmailVerificationErrorInstance = NewWrongEmailVerificationError(false, false, "")
13 changes: 0 additions & 13 deletions backend/domain/errors.go

This file was deleted.

10 changes: 3 additions & 7 deletions backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
module github.com/naohito-T/tinyurl/backend

go 1.21.1
go 1.21

require (
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1
github.com/labstack/echo/v4 v4.11.4
github.com/stretchr/testify v1.8.4
)

require (
github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
Expand All @@ -25,19 +24,16 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
1 change: 0 additions & 1 deletion backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
16 changes: 16 additions & 0 deletions backend/internal/rest/handler/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package handler

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/naohito-T/tinyurl/backend/domain/customerror"
)

func HealthHandler(c echo.Context) error {
a := true
if a {
return customerror.WrongEmailVerificationErrorInstance
}
return c.JSON(http.StatusOK, map[string]string{"message": "ok2"})
}
11 changes: 11 additions & 0 deletions backend/internal/rest/handler/shorturl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package handler

import (
"net/http"

"github.com/labstack/echo/v4"
)

func ShortURLHandler(c echo.Context) error {
return c.String(http.StatusOK, "OK")
}
40 changes: 22 additions & 18 deletions backend/internal/rest/middleware/error/error.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
package error

// see: https://go.dev/play/p/TzZE1mdL63_1

import (
"net/http"
"errors"

"github.com/labstack/echo/v4"
"github.com/naohito-T/tinyurl/backend/domain/customerror"
)

// これはGo言語における型アサーション(type assertion)の一例です。型アサーションは、インターフェースの値が特定の型を持っているかどうかをチェックし、その型の値を取り出すために使用されます。
// このコードの場合、err はエラーを表すインターフェース型です。*echo.HTTPError は echo パッケージの HTTPError 型のポインタです。この行は、err が *echo.HTTPError 型の値を持っているかをチェックし、もしそうであれば he にその値を割り当て、ok に true を設定します。err が *echo.HTTPError 型でなければ、ok は false になり、he は nil になります。
// この型アサーションを使うことで、安全に型変換を行いつつ、エラーチェックを同時に実施できます。
func CustomErrorHandler(err error, c echo.Context) {
he, ok := err.(*echo.HTTPError)
if !ok {
he = echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}

c.Logger().Error("カスタムエラーに入ったよ")
// ロギング
c.Logger().Error(err)
// c.Logger().Error(err)
appErr := buildError(err, c)
c.JSON(appErr.Status, map[string]string{"code": appErr.Code, "message": appErr.Message})

Check failure on line 20 in backend/internal/rest/middleware/error/error.go

View workflow job for this annotation

GitHub Actions / lint-backend

Error return value of `c.JSON` is not checked (errcheck)
}

// エラータイプによって条件分岐
if he.Code == http.StatusNotFound {
// リダイレクトを行う例
if err := c.Redirect(http.StatusTemporaryRedirect, "/some-fallback-url"); err != nil {
c.Logger().Error(err)
}
} else {
// エラーレスポンスを送信する例
if err := c.JSON(he.Code, map[string]string{"message": he.Message.(string)}); err != nil {
c.Logger().Error(err)
}
func buildError(err error, c echo.Context) *customerror.ApplicationError {
appErr := customerror.ApplicationError{
Status: customerror.UnexpectedCode.Status,
Code: customerror.UnexpectedCode.Code,
Message: customerror.UnexpectedCode.Message,
}
if errors.Is(err, customerror.WrongEmailVerificationErrorInstance) {
c.Logger().Error("これがWrongEmailVerificationErrorアプリケーションエラー")
// return &customerror.ApplicationError{}
}
return &appErr
}
18 changes: 14 additions & 4 deletions backend/internal/rest/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package middleware

// see: https://echo.labstack.com/docs/category/middleware

import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/naohito-T/tinyurl/backend/internal/rest/middleware/accesslog"
"github.com/naohito-T/tinyurl/backend/internal/rest/middleware/error"

// "github.com/naohito-T/tinyurl/backend/internal/rest/middleware/accesslog"
ehandler "github.com/naohito-T/tinyurl/backend/internal/rest/middleware/error"
)

// loggerの考え方
// https://yuya-hirooka.hatenablog.com/entry/2021/10/15/123607
func CustomMiddleware(e *echo.Echo) {
e.Use(accesslog.AccessLog())
e.HTTPErrorHandler = error.CustomErrorHandler
// e.Use(accesslog.AccessLog())
// expect this handler is used as fallback unless a more specific is present
e.Use(middleware.Recover())
// これでechoのloggerを操作できる。
// e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
// Format: "time=${time_rfc3339_nano}, method=${method}, uri=${uri}, status=${status}\n",
// }))
e.HTTPErrorHandler = ehandler.CustomErrorHandler
}
11 changes: 11 additions & 0 deletions backend/internal/rest/middleware/security/security.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package security

import (
"github.com/labstack/echo/v4"
)

func AttachSecurity(e *echo.Echo) {
// サーバー起動時のバナーを非表示にする
e.HideBanner = true
e.HidePort = true
}
14 changes: 11 additions & 3 deletions backend/internal/rest/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@ import (
"net/http"

"github.com/labstack/echo/v4"
Router "github.com/naohito-T/tinyurl/backend/configs"
"github.com/naohito-T/tinyurl/backend/internal/infrastructures/slog"
"github.com/naohito-T/tinyurl/backend/internal/rest/handler"
// "github.com/naohito-T/tinyurl/backend/internal/rest/container"
)

func catchAllHandler(c echo.Context) error {
return c.JSON(http.StatusNotFound, map[string]string{"message": "Route not found"})
}

// https://tinyurl.com/app/api/url/create"
// NewRouter これもシングルトンにした場合の例が気になる
func NewRouter(e *echo.Echo) {
// container := container.NewGuestContainer()
e.GET(Router.Health, handler.HealthHandler)
e.GET(Router.GetShortURL, hello)
e.POST(Router.CreateShortURL, hello)

e.GET("/health", hello)
e.GET("/api/v1/urls/:shortUrl", hello)
e.POST("/api/v1/urls", hello)
// 未定義のルート用のキャッチオールハンドラ
e.Any("/*", catchAllHandler)
}

func hello(c echo.Context) error {
Expand Down
9 changes: 0 additions & 9 deletions backend/staticcheck.conf

This file was deleted.

Loading

0 comments on commit 6cebb61

Please sign in to comment.