3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Go]Genericsを使ってWebアプリの共通処理をまとめて行う

Posted at

最近、FiberというWeb Application FrameworkでWebアプリを作っていますが、そこでGenericsを使ってWebリクエストを処理する「お約束」みたいな共通処理をまとめてやっています。

Fiberじゃなくても使えると思いますが、Fiberを例にしています。なお、Fiberは現在v3がベータ版ですが、以下はv2のコードです。

リクエストごとに型を作る

最初にリクエストごとの型を作ります。

type TestGetApiReq struct {
	Name string `json:"name" query:"name"`
}

type TestPostApiReq struct {
	Country string `json:"country"`
}

GETのほうは、QUERY_STRINGとして渡されることを想定しています(json:"name"は、レスポンスに使うためなので、普通は不要です)。POSTの方は、RequestBodyにJSONがくる想定です。
Fiberでは、QueryParserBodyParserでパースするために必要なので、作るのに違和感はないと思います(他のWAFでも、似たようなことはするでしょう)。

Requestの型がRequestWrapperというinterfaceを満たすようにする

RequestWrapperというinterfaceを作ります。以下のようなもので、特に何もしません。

type RequestWrapper interface {
	CanWrap() 
}

先程の型に実装します。

func (TestGetApiReq) CanWrap()  {}
func (TestPostApiReq) CanWrap() {}

これは、Genericsとして、Requestwrapperを渡したいだけなので、特に意味はないです。
CustomContextを実装するなどして、Cc() *context.CustomContext を返すようなものを作ると、より有意義かと思います。

WrapGetRequest/WrapPostRequestという型を作る

*fiber.CtxRequestWrpper interfaceを満たしている型を受け取れる型です。

type WrapGetRequest[T RequestWrapper] struct {
	c   *fiber.Ctx
	req *T
}

type WrapJsonRequest[T RequestWrapper] struct {
	c   *fiber.Ctx
	req *T
}

newも作りましょう。

func NewWrapGetRequest[T RequestWrapper](c *fiber.Ctx, req *T) *WrapGetRequest[T] {
	return &WrapGetRequest[T]{c, req}
}

func NewWrapJsonRequest[T RequestWrapper](c *fiber.Ctx, req *T) *WrapJsonRequest[T] {
	return &WrapJsonRequest[T]{c, req}
}

GET/POSTごとの共通処理をPreProcessに書く

GETの場合は、QueryParserでリクエストを処理します。POSTの場合は、BodyParserでリクエストを処理します。

今回のは、サンプルなので、それ以外何もしませんが、例えば、WrapGetRequestWrapPostRequestにカスタムコンテキストを持たせたることもできるでしょうし、その他の共通処理を書くこともできます。

func (w *WrapGetRequest[T]) PreProcess() (error, bool) {
	err := w.c.QueryParser(w.req)
	if err != nil {
		return  w.c.Status(400).SendString(err.Error()), false
	}

	return nil, true
}

func (w *WrapJsonRequest[T]) PreProcess() (error, bool) {
	err := w.c.BodyParser(w.req)
	if err != nil {
		return  w.c.Status(400).SendString(err.Error()), false
	}

	return nil, true
}

実際の処理を書く

サンプルなので、mainの中に実装しています。
GET/POSTの2つのAPIを作りましたが、リクエストでもらったものをJSONで返すだけです。

func main() {
	// Initialize a new Fiber app
	app := fiber.New()

	// Define a route for the GET method on the root path '/'
	app.Get("/name", func(c *fiber.Ctx) error {
		req := new(TestGetApiReq)
		w := NewWrapGetRequest(c, req)
		err, ok := w.PreProcess()
		if !ok {
			return err
		}
		return c.JSON(req)
	})
	app.Post("/country", func(c *fiber.Ctx) error {
		req := new(TestPostApiReq)
		w := NewWrapJsonRequest(c, req)
		err, ok := w.PreProcess()
		if !ok {
			return err
		}
		return c.JSON(req)
	})

    app.Listen(":3000")
}

サーバ動かすと動作確認がめんどくさいので、テストでレスポンスを表示するものを書いたものをGistにおいておきました。

動かすと、以下のような結果が出力されます。

2024/10/19 21:23:41 Status: 200 OK, Body:{"name":"ktat"}
2024/10/19 21:23:41 Status: 200 OK, Body:{"country":"Japan"}
2024/10/19 21:23:41 Status: 400 Bad Request, Body:unexpected end of JSON input

最後のは、壊れたJSONを送ったものです。

終わり

これで、Webにまつわるお約束みたいな前処理を共通処理できるようになりますね。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?