echoでstrapi REST APIのようなパラメータを受ける

# 最初に
フロントからの特定のリソースに対して検索リクエストが飛んでくる。
そのリクエストはechoで動くサーバがさばく。
検索クエリはstrapi REST API(ここでは以下strapi API)のようになっていることが要件で
汎用性 <【優先度】< ビジネスロジックとのマッチ とすることが条件となっている。
そうした場合、こうやって組みましたの一例を紹介する。

# リクエスト内容を分解する
strapi APIでは、ある規格化されたリクエストパラメータを送ることで
柔軟な検索リクエストを受けることができる。

パラメータ参考;[Parameters | Strapi Documentation](https://docs.strapi.io/dev-docs/api/rest/parameters)

その処理を模すには、まず処理できる形にリクエストを分解しなければいけない。
その内容について、書いていく。

## controller

リクエストを受け取った後、parameters構造体に当てはまるよう分解するメソッドに渡す

~~~go
import (
	search "app/api/request"
	"github.com/labstack/echo/v4"
)

func (hc *ProposalSampleController) Index(c echo.Context) error {
	queryParams := c.QueryParams()
	parameters := search.NewParameters(queryParams)
~~~

## app/api/request

NewParametersメソッド内でリクエストを分解する

~~~go
func NewParameters(queryParams url.Values) Parameters {
	// sort: 各章で記載
    
	// field: 各章で記載

	// pagination: 各章で記載

	// filter: 各章で記載

	return Parameters{

	}

}
~~~


パラメータの構造体を定義しておく

~~~go
    type Parameters struct {
    	Sort       search_param.Sort
    	Filter     search_param.Filter
    	Populate   string
    	Fields     search_param.Fields
    	Pagination search_param.Pagination
    }
~~~


### Sort
並び替え情報
例: `?sort[0]=title:desc`

構造体定義
~~~go
var Asc = "asc"
var Desc = "desc"

type SortParam struct {
	TargetTable string
	Target      string
	Sorting     string
}
type Sort struct {
	SortParam
}

~~~

分解の処理
~~~go
func NewParameters(queryParams url.Values) Parameters {
	// sort
	var sort search_param.Sort
    // ２重でソート条件をつけることはないのでsort[0]のみ対応する
	sortQueryParam := queryParams.Get("sort[0]")
	if sortQueryParam != "" {
		sortQueryParamArray := strings.Split(sortQueryParam, ":")
		sort.Target = sortQueryParamArray[0]
		if sortQueryParamArray[1] == search_param.Asc {
			sort.Sorting = search_param.Asc
		}
		if sortQueryParamArray[1] == search_param.Desc {
			sort.Sorting = search_param.Desc
		}
	}
	// ジョイン先の値でソートする場合は, 別で定義する
    // 例としてsizeのデータでソートを指定
    // ?sort[size]=xxx:asc
	sortSizeQueryParam := queryParams.Get("sort[size]")
	if sortSizeQueryParam != "" {
		sortSizeQueryParamArray := strings.Split(sortSizeQueryParam, ":")
		sort.TargetTable = "resource_size"
		sort.Target = sortSizeQueryParamArray[0]
		if sortSizeQueryParamArray[1] == search_param.Asc {
			sort.Sorting = search_param.Asc
		}
		if sortSizeQueryParamArray[1] == search_param.Desc {
			sort.Sorting = search_param.Desc
		}
	}
}

~~~



### Filter
絞り込み
例 `filters[field][operator]=value`

構造体
~~~go
type FilterParam struct {
	Query []string
	Val   string
}
type Filter []FilterParam
~~~

分解の処理
~~~go
func NewParameters(queryParams url.Values) Parameters {
	// filter
	var filter search_param.Filter
	for k := range queryParams { // 変数kにはmap（連想配列）のkeyが入る
        // filterのクエリのみ処理する
		r := regexp.MustCompile(`filter`)
		if !r.MatchString(k) {
			continue
		}

		val := queryParams.Get(k)
        // [field][operator] の部分を取得
		r2 := regexp.MustCompile(`\[(.*?)]`)
		getPram := r2.FindAllStringSubmatch(k, -1)

		var query []string
		for i := 0; i < len(getPram); i++ {
			query = append(query, getPram[i][1])
		}
		filter = append(filter, search_param.FilterParam{
			Query: query, // [field, operator] のような値が入っている
			Val:   val,
		})
	}

    // ~~~略~~~
}
~~~


### Populate
付加情報
例　`?populate=* / ?populate=Tag`

引数1個想定なので、そのままパラメータ取得値を構造体に当てる
~~~go
func NewParameters(queryParams url.Values) Parameters {
	return Parameters{
		Populate:   queryParams.Get("populate"),
	}
}
~~~


### Fields
表示項目
例　`?fields[0]=title&fields[1]=body`

構造体
~~~go
type Fields []string
~~~

分解の処理
~~~go
func NewParameters(queryParams url.Values) Parameters {
	// field
	var field search_param.Fields
    // 配列のキー番号が連番になっている前提
	for i := 0; ; i++ {
		key := fmt.Sprintf("fields[%d]", i)
		value := queryParams.Get(key) 
		if len(value) == 0 {
			break
		}
		field = append(field, value)
	}
   // 省略
}
~~~



### Pagination
ページネーション
例: `?pagination[page]=1&pagination[pageSize]=10&pagination[withCount]=true`

構造体
~~~go
type Pagination struct {
	Page      int
	PageSize  int
	WithCount bool
}
~~~

分解の処理
~~~go
func NewParameters(queryParams url.Values) Parameters {
	// pagination
	var pagination search_param.Pagination
	page := queryParams.Get("pagination[page]")
	pagination.Page, _ = strconv.Atoi(page)
	pageSize := queryParams.Get("pagination[pageSize]")
	pagination.PageSize, _ = strconv.Atoi(pageSize)
	withCount := queryParams.Get("pagination[withCount]")
	if withCount == "true" {
		pagination.WithCount = true
	} else {
		pagination.WithCount = false
	}
    // 略
}
~~~




# まとめ
strapiのような検索リクエストをGoで定義した構造体に当てていく処理を紹介した。
また、この検索リクエストParameters構造体を使ったクエリの組み立て部分は別記事で紹介したい。


---
弊社では、一緒に働いてくれるエンジニアを募集しています。


↓興味のある方はこちらから↓
[Wantedly - スタジオアンビルト株式会社](https://www.wantedly.com/companies/studiounbuilt)

こんなWebサービスを作っています。
・[マドリー](https://madree.jp/)
・[Studio Unbuilt](https://www.studiounbuilt.com/)