ginを最速でマスターしよう

# 初めに:sunny:
![gin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/c6d6b737-5fcc-acd7-79e0-f686e0c61d9a.png)

### ginとは何でしょうか
**ginはGo（Golang）で書かれたWebフレームワークです。 httprouterのおかげで、最大40倍高速なパフォーマンスを備えたmartiniのようなAPIを備えています。パフォーマンスと優れた生産性が必要な場合は、Ginを好きになるでしょう。**--[公式ドキュメント](https://github.com/gin-gonic/gin)

パフォーマンスが良いのが売りらしいですが、他のGo言語のwebフレームワークと比較してみます。
## 人気ランキング

> githubのスターの多い順

| Name | Stars | Forks | Issues Open | Issues Closed  | Birth Year | Latest Update | Author |
|---|---|---|---|---|----|----|----|
| Gin | 34,231 | 3,900 | 183 |  1,115 |  2014 | 2019-12-27 | [@manucorporat](https://github.com/manucorporat) |
| Beego | 22,890 | 4,600 | 737 | 1,900 | 2012  | 2019-12-27 | [@astaxie](https://github.com/astaxie) |
| Iris | 17,133 | 1,906 | 4 |  467 | 2016 | 2019-12-27 | [@kataras](https://github.com/kataras) |
| Echo | 16,058 | 1,541 | 26 |  902 |  2015 | 2019-12-27 | [@vishr](https://github.com/vishr) |

[その他の比較詳細](https://github.com/speedwheel/awesome-go-web-frameworks/blob/master/README.md)

また、国内に案件が存在するのは、現在GinとEchoだけです:point_up:

# 準備
作業PCにGo言語が入ってることをまず確認してください。

```
go version
go version go1.13
```

ginをgetしましょう、新規プロジェクトを作る際にgomodを使用することをお勧めします。
gomodについてよくわからない方はこちらの記事を参考にしてください。
[GOMODULE--Goのパッケージ管理](https://qiita.com/Syoitu/items/f221b52231703cebe8ff)

新規フォルダを作ってください、フォルダ名は任意で結構です。

```shell
mkdir gin_test && cd gin_test
```
go modを初期化します。

```shell
go mod init gin_test
```
初期化完了したら、ginのパッケージを取得します

```
go get -u github.com/gin-gonic/gin
```

# ginの基礎
ginはlaravelやDjangoのようなフルスタックフレームワークと違って非常にライトなフレームワークになります、
最初の実例を書いてみよう。

```go:main.go
package main

import "github.com/gin-gonic/gin"

import "net/http"

func main() {
	engine:= gin.Default()
	engine.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "hello world",
		})
	})
	engine.Run(":3000")
}
```

`go run main.go`でサーバー立ち上げて `localhost:3000`にアクセスしてみると
Jsonメッセージの ` {"message":"hello world"} `が確認できます。

もしginを使わずに素のGoで同じサーバーを作る場合、以下のようになります。

```go:main.go

package main

import (
	"encoding/json"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, q *http.Request) {
		message := map[string]string{
			"message": "hello world",
		}
		jsonMessage, err := json.Marshal(message)
		if err != nil {
			panic(err.Error())
		}
		w.Write(jsonMessage)
	})
    http.ListenAndServe("127.0.0.1:3000", mux)
}
```
コードの量はほぼ倍になりました。
では、ginは実際何をしたのでしょうか、ginのソースコード見てみます。

```go:context.go
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params
	handlers HandlersChain
	index    int8
	fullPath string

	engine *Engine

	// Keys is a key/value pair exclusively for the context of each request.
	Keys map[string]interface{}

	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
	Errors errorMsgs

	// Accepted defines a list of manually accepted formats for content negotiation.
	Accepted []string

	// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
	queryCache url.Values

	// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
	// or PUT body parameters.
	formCache url.Values
}
```
素のGoで `HandleFunc`を書く際に、パラメタとして存在する `writermem responseWriter`や `Request   *http.Request`、ginの `Context`の `struct`の構成要素になってます。

```go:gin.go
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}
```

`engin.Run(":3000")`も実際 `http.ListenAndServe(address, engine)`を中身で呼んでます。
Goのhttpサーバーのコードを活かして、便利な処理を追加するのがginです。
素のGoでhttpサーバー書いたことがあれば、ginのソースコードは非常にわかやすいはずです

# ミドルウェアを使ってみよう
ginはリクエストに対する処理は基本下記のようになります

```
Request -> Route Parser -> Middleware -> Route Handler -> Middleware -> Response
```
実際処理する関数に到達する前に、必ずミドルウェアを通る必要があります、
簡単な例として、アクセスするユーザーの`User-Agent` を取得するミドルウェアを作ってみましょう。

```go:main.go
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	engine:= gin.Default()
	ua := ""
     // ミドルウェアを使用
	engine.Use(func(c *gin.Context) {
		ua = c.GetHeader("User-Agent")
		c.Next()
	})
	engine.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message":    "hello world",
			"User-Agent": ua,
		})
	})
	engine.Run(":3000")
}
```

`http://localhost:3000/`にアクセスすれば、以下のメッセージが確認できるはずです。

```json
{
User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
message: "hello world"
}
```

# HTMLや静的ファイルを扱う
### HTMLの扱い

```shell
mkdir templates && cd templates && touch index.html
```
index.htmlの中身は以下のように

```html:index.html
<h1 style="color: rebeccapurple;">{{.message}}</h1>
```
main.goを修正

```go:main.go
package main

import "github.com/gin-gonic/gin"

import "net/http"

func main() {
	engine:= gin.Default()
    // htmlのディレクトリを指定
	engine.LoadHTMLGlob("templates/*")
	engine.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
             // htmlに渡す変数を定義
			"message": "hello gin",
		})
	})
	engine.Run(":3000")
}
```
サーバー立ち上げて`localhost:3000`にアクセルすると、以下の内容が確認できるはずです![UNADJUSTEDNONRAW_mini_3e.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/473ceefa-6506-3901-ca98-e2bb0f9487f0.jpeg)

### 静的ファイルを扱う

```shell
mkdir static 
```
任意の画像をstaticフォルダに入れます。
![2019-12-27 17.35のイメージ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/4d71deb4-4753-3b7f-58cc-61aec433c264.jpeg)
main.goを以下のように修正します。

```go:main.go
package main

import "github.com/gin-gonic/gin"

func main() {
  engine:= gin.Default()
  engine.Static("/static", "./static")
  engine.Run(":3000")
}
```
サーバーを立ち上げて、`http://localhost:3000/static/gin.png`をアクセスすると、画像が表示されるはずです。

# ファイルのアップロード

まず画像保存用のフォルダを作っておきます。

```
mkdir images
```

```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	"io"
	"log"
	"net/http"
	"os"
)

func main(){
	engine := gin.Default()
	engine.POST("/upload", func(c *gin.Context) {
		file,header, err :=  c.Request.FormFile("image")
		if err != nil {
			c.String(http.StatusBadRequest, "Bad request")
			return
		}
		fileName := header.Filename
		dir, _ := os.Getwd()
		out, err := os.Create(dir+"\\images\\"+fileName)
		if err != nil {
			log.Fatal(err)
		}
		defer out.Close()
		_, err = io.Copy(out, file)
		if err != nil {
			log.Fatal(err)
		}
		c.JSON(http.StatusOK, gin.H{
			"status": "ok",
		})
	})
	engine.Run(":3000")

}
```
フロントエンド、vue.jsを使ってます。 urlが `/api/upload`になってるのは`proxy`の設定に`/api`で始まるurlの`target`をginサーバーにリダイレクトしています。設定の仕方に疑問のある方はコメントください

```vue:App.vue
<template>
  <div id="app">
          <input type="file" id="people-export" ref="input">
          <button type="submit" @click="fileUpload">提出</button>
  </div>
</template>
<script>
import axios from 'axios'

export default {
  name: 'app',
  components: {
    HelloWorld
  },
  methods: {
    fileUpload(){
      let file = this.$refs.input;
      let formData = new FormData();
      formData.append('image', file.files[0]);
      axios({
        method: 'post',
        url :'/api/upload',
        data : formData,
        header :{
           'Content-Type': 'multipart/form-data',
        }
      }).then(( res )=>{
        console.log(res.data)
      })
    }
  }
}
</script>
<style>
</style>
```
# ginで簡単なREST風のAPIサーバーを作ってみよう

#### 実装機能、書籍についての
- 　新規追加
- 　データ一覧
- 　データ修正
- 　削除

#### ディレクトリ構成
```
qiita
|- controller
|- |- book.go
|- middleware
|- |- bookMiddleware.go
|- model
|- |- book.go
|- service
|- |- book.go
|- |- init.go
|- go.mod
|- main.go
```
必要なパッケージをgetします。

```shell
go get github.com/gin-gonic/gin
go get github.com/go-sql-driver/mysql
go get github.com/go-xorm/xorm
go get go.uber.org/zap
```
#### main.go
役割はREST風のAPIを実装とmysql用のパッケージの初期化します。

```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"qiita/controller"
	"qiita/middleware"
)

func main(){
	engine := gin.Default()
	// ミドルウェア
	engine.Use(middleware.RecordUaAndTime)
	// CRUD 書籍
	bookEngine := engine.Group("/book")
	{
		v1 := bookEngine.Group("/v1")
		{
			v1.POST("/add", controller.BookAdd)
			v1.GET("/list", controller.BookList)
			v1.PUT("/update", controller.BookUpdate)
			v1.DELETE("/delete", controller.BookDelete)
		}
	}
	engine.Run(":3000")
}
```
#### controller/book.go
機能としてはmain.goから振られたリクエストをserviceにハンドルし、レスポンスを返します。

```go:controller/book.go
package controller

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"qiita/model"
	"qiita/service"
	"strconv"
)

func BookAdd(c *gin.Context) {
     book := model.Book{}
     err := c.Bind(&book)
     if err != nil{
		 c.String(http.StatusBadRequest, "Bad request")
		 return
	 }
	bookService :=service.BookService{}
	err = bookService.SetBook(&book)
	if err != nil{
		c.String(http.StatusInternalServerError, "Server Error")
		return
	}
	c.JSON(http.StatusCreated, gin.H{
		"status": "ok",
	})
}

func BookList(c *gin.Context){
	bookService :=service.BookService{}
	BookLists := bookService.GetBookList()
    c.JSONP(http.StatusOK, gin.H{
    	"message": "ok",
    	"data": BookLists,
	})
}

func BookUpdate(c *gin.Context){
	book := model.Book{}
	err := c.Bind(&book)
	if err != nil{
		c.String(http.StatusBadRequest, "Bad request")
		return
	}
	bookService :=service.BookService{}
	err = bookService.UpdateBook(&book)
	if err != nil{
		c.String(http.StatusInternalServerError, "Server Error")
		return
	}
	c.JSON(http.StatusCreated, gin.H{
		"status": "ok",
	})
}

func BookDelete(c *gin.Context){
    id := c.PostForm("id")
	intId, err := strconv.ParseInt(id, 10, 0)
	if err != nil{
		c.String(http.StatusBadRequest, "Bad request")
		return
	}
	bookService :=service.BookService{}
	err = bookService.DeleteBook(int(intId))
	if err != nil{
		c.String(http.StatusInternalServerError, "Server Error")
		return
	}
	c.JSON(http.StatusCreated, gin.H{
		"status": "ok",
	})
}
```
#### middleware/bookMiddleware.go
リクエストのlogを記録します。
 `go.uber.org/zap`パッケージを使用しています、出前で有名なUberさんのオープンソースらしいです。

```go:middleware/bookMiddleware.go
package middleware

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"log"
	"time"
)

func RecordUaAndTime(c *gin.Context){
   logger, err := zap.NewProduction()
   if err != nil{
   	  log.Fatal(err.Error())
   }
   oldTime := time.Now()
   ua := c.GetHeader("User-Agent")
   c.Next()
	logger.Info("incoming request",
		zap.String("path", c.Request.URL.Path),
		zap.String("Ua", ua),
		zap.Int("status", c.Writer.Status()),
		zap.Duration("elapsed", time.Now().Sub(oldTime)),
	)
}
```
#### model/book.go
bookの構造体を定義してます。
`xorm`パッケージ使用して、テーブルの初期化でも使用します。

```go:model/book.go
package model

type Book struct {
	Id    int64  `xorm:"pk autoincr int(64)" form:"id" json:"id"`
	Title string `xorm:"varchar(40)" json:"title" form:"title"`
	Content string `xorm:"varchar(40)" json:"content" form:"content"`
}
```

#### service/init.go

データベースへの接続とテーブルの初期化を実装します。

```go:service/init.go
package service

import (
	"errors"
	"fmt"
	"github.com/go-xorm/xorm"
	"qiita/model"
	"log"
)

var DbEngine *xorm.Engine

func init()  {
	driverName := "mysql"
	DsName := "root:root@(192.168.99.100:3306)/gin?charset=utf8"
	err := errors.New("")
	DbEngine, err = xorm.NewEngine(driverName,DsName)
	if err != nil && err.Error() != ""{
		log.Fatal(err.Error())
	}
	DbEngine.ShowSQL(true)
	DbEngine.SetMaxOpenConns(2)
	DbEngine.Sync2(new(model.Book))
	fmt.Println("init data base ok")
}
```
#### service/book.go
機能としてはコントローラから振られたdb操作を引き受け、結果を返します。

```go:service/book.go
package service

import (
	"qiita/model"
)

type BookService struct {}

func (BookService) SetBook(book *model.Book) error {
	_, err := DbEngine.Insert(book)
	if err!= nil{
         return  err
	}
	return nil
}


func (BookService) GetBookList() []model.Book {
	tests := make([]model.Book, 0)
	err := DbEngine.Distinct("id", "title", "content").Limit(10, 0).Find(&tests)
	if err != nil {
		panic(err)
	}
    return tests
}

func (BookService) UpdateBook(newBook *model.Book) error {
    _, err := DbEngine.Id(newBook.Id).Update(newBook)
	if err != nil {
		return err
	}
    return nil
}

func (BookService) DeleteBook(id int) error {
	book := new(model.Book)
	_, err := DbEngine.Id(id).Delete(book)
	if err != nil{
		return err
	}
	return nil
}
```
サーバを立ち上げて、APIの動作を見てみます。

```shell
go run main.go
```
起動後、以下のlogも確認できるはずです。
内容はdbの初期化完了と実装されたAPI情報です。

```shell
[xorm] [info]  2020/01/01 00:40:53.621508 [SQL] SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE
_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB') [gin]
[xorm] [info]  2020/01/01 00:40:53.655668 [SQL] SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`, `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION
_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? [gin book]
[xorm] [info]  2020/01/01 00:40:53.656645 [SQL] SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NA
ME` = ? [gin book]
init data base ok
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /book/v1/add              --> qiita/controller.BookAdd (4 handlers)
[GIN-debug] GET    /book/v1/list             --> qiita/controller.BookList (4 handlers)
[GIN-debug] PUT    /book/v1/update           --> qiita/controller.BookUpdate (4 handlers)
[GIN-debug] DELETE /book/v1/delete           --> qiita/controller.BookDelete (4 handlers)
[GIN-debug] Listening and serving HTTP on :3000
```
APIのテストは`postman`を使います。

#### 新規追加 POST /book/v1/add
<img width="723" alt="1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/9c06af3d-87da-f5b0-eaab-f35ea3732d1e.png">
データベースにもちゃんとデータが入りました。
<img width="614" alt="2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/4c702e33-15f2-4144-de5c-790c8e26e0ba.png">

#### データ一覧 GET /book/v1/list
<img width="716" alt="3.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/f2526c9d-73df-649a-d7ba-3175bdd89f2e.png">

#### データ修正 PUT /book/v1/update

内容を一部修正します。
<img width="720" alt="4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/f67693a5-5f57-e8ad-e5fe-5c7ace964acb.png">

データベースにもちゃんと反映されてます。
<img width="628" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/0b7f7ecd-b819-6f5a-5b7a-939a2bb57652.png">

#### データの削除 DELETE /book/v1/delete

<img width="715" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/2444b53f-98bf-5a39-4244-f8bdbe759e12.png">

データベース内のデータも削除されました。
<img width="634" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/9e7fbbeb-e90b-36c9-4e1a-aa3c484c4bb6.png">

長くなりましたが、以上となります。
ORMに関しては `xorm` パッケージを使用してます、ドキュメントは公式リポジトリを参考にしてください -- [リンク](https://github.com/go-xorm/xorm)

# 最後に

最後まで読んで下さり、ありがとうございます。

実際ginを使ってみて、ライトな感覚に結構惹かれています。
フレームワークを使用してるけれど、過依存することもなく、素のGoの感覚でコーディングできます。

# 2020/09/11の追記
Ginに関する内容を追記します。
【簡単なREST風のAPIサーバー】と被る内容もありますが本記事をクックブックとして読みやすくするために敢えて抜粋しています。

## Getリクエストからクエリを取得

```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main()  {
	serve := gin.Default()
	serve.GET("/test", func(c *gin.Context) {
		firstName := c.Query("first_name")
		lastName := c.DefaultQuery("last_name", "default_name")
		c.JSON(http.StatusOK, gin.H{"firstName":firstName, "lastName":lastName})
	})
	serve.Run("127.0.0.1:8080")
}
```

サーバーを立ち上げ、`http://localhost:8080/test?first_name=山田`にアクセスすると下記のような結果になります。
`DefaultQuery`関数を使用すれば、クエリ欠損する場合は設定したデフォルト値を使用します。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/49b61a60-aad4-eb77-3faf-959c1f9753a3.png)

## PostリクエストからFormデータを取得

```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main()  {
	serve := gin.Default()
	serve.POST("/test", func(c *gin.Context) {
		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name", "default_last_name")

		c.JSON(http.StatusOK, gin.H{"firstName": firstName, "lastName": lastName})
	})
	serve.Run("127.0.0.1:8080")
}
```
サーバーを立ち上げ、`127.0.0.1:8080/test`にPOSTリクエスト送信すれば、以下のレスポンスが戻ってきます。
:sunny:テストツールはPOSTMAN使用をしています。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/d6a3139e-cd09-65e8-f72f-98839bb56009.png)

## データのバリデーション

### 構造体を使用したバリデーション
```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
)

type Person struct {
	Age int `form:"age" validate:"required,gt=10,max=100"`
	Name string `form:"name" validate:"required"`
	Address string `form:"address" validate:"required"`
}

func main()  {
	serve := gin.Default()
	serve.GET("/testing", func(c *gin.Context) {
         var person Person
         if err := c.ShouldBind(&person);err!=nil{
         	//c.String(500, "%v",err)
         	c.JSON(500, gin.H{"msg": err.Error()})
			return
		 }
		 //c.String(200, "%v", person)
		 c.JSON(200, gin.H{"person": person})
	})
	serve.Run("127.0.0.1:8080")
}
```
構造体を使用してバリデーション行う際に、検証条件をタグの`validate`に追加すれば、リクエスト本文をバインドする際に自動的にバリデーション行えます。
また、複数の検証条件がある際に`,`を使て分けることができます。

サーバーを立ち上げて`http://127.0.0.1:8080/testing?age=11&name=山田&address=どこか`にGetリクエストを送信。
ageは11になってるため、以下の結果が帰ってきました。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/b0b960e5-0bfa-4934-20a3-a6512dd4b845.png)
ageを9に修正して、再度リクエスト送信しますと、500エラーが戻ってきました。
認証ルールちゃんと機能してます。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/919174aa-a63e-cd36-9052-81dd00f4f990.png)


:point_down_tone1:下記のように、子構造体の検証条件を使用する際に、親構造体の検証条件に`dive`を追加する必要があります。

```go
...
type user struct {
	Name  string `json:"name" binding:"required"`
	Email string `json:"email" binding:"required,email"`
}
 
type data struct {
	User []user `json:"user" binding:"required,dive"` // use dive tag
}
...
```
:point_right_tone1:検証条件一覧は [公式ドキュメント](https://godoc.org/gopkg.in/go-playground/validator.v8)を参考にしてください。
### 検証条件のカスタマイズ
検証条件のカスタマイズをするには`validator`パッケージが必要です、本記事はv10系使用してます。
`go get gopkg.in/go-playground/validator.v10`でインストールすることができます。[ドキュメント公式サイト](https://gopkg.in/go-playground/validator.v10)

```go:mian.go
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"net/http"
	"time"
)
// bookabledateというカスタマイズ検証条件を追加
type Booking struct {
   CheckIn time.Time `form:"check_in" validate:"required,bookabledate" time_format:"2006-01-02"`
   CheckOut time.Time `form:"check_out" validate:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

//カスタマイズ検証条件の関数
//チェックインする時間は現在時間より先である必要があります
func bookableDate(fl validator.FieldLevel) bool {
	today:=time.Now()
	if  date,ok:=fl.Field().Interface().(time.Time);ok{
		if date.Unix()>today.Unix(){
			fmt.Println("date unix ：",date.Unix())
			return true
		}
	}
	return false
}

func main()  {
	serve := gin.Default()

    v := validator.New()
    v.RegisterValidation("bookabledate", bookableDate)

	serve.GET("/bookable", func(c *gin.Context) {
		var b Booking
		if err := c.ShouldBind(&b);err!=nil{
			c.JSON(500,gin.H{"error":err.Error()})
			c.Abort()
			return
		}
		if err := v.Struct(b); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": err.Error(),
			})
			c.Abort()
			return
		}
		c.JSON(http.StatusOK, gin.H{"message": "ok!", "booking":b})
	})

	serve.Run("127.0.0.1:8080")
}
```
`bookableDate`というカスタマイズ検証条件関数では現在時間のタイムスタンプとチェックイン時間のを比較し、チェックイン時間の方が大きければfalseを返します。

サーバーを立ち上げ、`http://localhost:8080/bookable?check_in=2020-09-13&check_out=2020-09-15`へリクエスト送信。
check_inの値が`2020-09-13`のタイムスタンプが現在時間(記事の更新時間)より大きいため、trueが戻ります。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/171a6ad6-33bb-5cbd-ae66-689839f03cf0.png)
check_inを十年前の`2010-09-13`に修正して、再度リクエスト送信すると検証に引っかかりました。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/13018c2e-f9d1-9344-a77b-d5d7fcf4cf42.png)

### バリデーションの多言語対応

検証条件のカスタマイズと同じように`validator`パッケージが必要です。
インストール: `go get gopkg.in/go-playground/validator.v10`

```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	en2 "github.com/go-playground/locales/en"
	ja2 "github.com/go-playground/locales/ja"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	en_translations "github.com/go-playground/validator/v10/translations/en"
	ja_translations "github.com/go-playground/validator/v10/translations/ja"
	"net/http"
)

type Person struct {
	Age int `form:"age" validate:"required,gt=10"`
	Name string `form:"name" validate:"required"`
	Address string `form:"address" validate:"required"`
}

var (
	Uni *ut.UniversalTranslator
	Validate *validator.Validate
)

func main()  {
	Validate = validator.New()
	ja := ja2.New()
	en := en2.New()
	Uni = ut.New(ja, en)
	serve := gin.Default()
	serve.GET("/testing", func(c *gin.Context) {
		locale := c.DefaultQuery("locale", "ja")
		trans, _ := Uni.GetTranslator(locale)
		switch locale {
		case "ja":
			ja_translations.RegisterDefaultTranslations(Validate,trans)
		case "en":
			en_translations.RegisterDefaultTranslations(Validate,trans)
		default:
			ja_translations.RegisterDefaultTranslations(Validate,trans)
		}
		var person Person
		if err := c.ShouldBind(&person);err!=nil{
			c.JSON(http.StatusInternalServerError, gin.H{"err": err.Error()})
			c.Abort()
			return
		}
		if err := Validate.Struct(person);err!=nil{
			errs := err.(validator.ValidationErrors)
			sliceErrs :=[]string{}
			for _, e := range errs{
				sliceErrs = append(sliceErrs, e.Translate(trans))
			}
			c.JSON(http.StatusInternalServerError, gin.H{"err": sliceErrs})
			c.Abort()
			return
		}
		c.JSON(http.StatusOK, gin.H{"msg": person})
	})
    serve.Run("127.0.0.1:8080")
}
```
サーバーを起動し、`http://localhost:8080/testing?locale=en`にアクセスすると、エラーメッセージが英語になっています。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/b03f4404-88c8-d2a8-012f-bfb37103fcb9.png)

localeをjaにすれば、エラーメッセージが日本語になります。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/b029ed4e-8875-cb81-ba7a-70fb8ff34835.png)
また、何も渡さない場合でも、エラーメッセージが日本語になります。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/4ce74e79-f707-de7e-3f1f-e916a459bcfc.png)