Skip to content

Commit

Permalink
Merge pull request #24 from rabee-inc/feature/rapi
Browse files Browse the repository at this point in the history
routing , handler を簡単に書けるライブラリ実装
  • Loading branch information
simiraaaa authored Jul 3, 2023
2 parents 8a9a49b + bccade2 commit 99f7ba9
Show file tree
Hide file tree
Showing 8 changed files with 698 additions and 0 deletions.
39 changes: 39 additions & 0 deletions rapi/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package rapi

import (
"net/http"

"github.com/go-chi/chi"
)

type Router interface {
http.Handler
GetChiRouter() chi.Router
Route(pattern string, fn func(r Router)) Router
SetAuthMiddleware(middlewares ...func(http.Handler) http.Handler)
SetOptAuthMiddleware(middlewares ...func(http.Handler) http.Handler)
Use(middlewares ...func(http.Handler) http.Handler)
With(middlewares ...func(http.Handler) http.Handler) Router
Auth() Router
OptAuth() Router
Connect(pattern string, re RouterElement)
Delete(pattern string, re RouterElement)
Get(pattern string, re RouterElement)
Head(pattern string, re RouterElement)
Options(pattern string, re RouterElement)
Patch(pattern string, re RouterElement)
Post(pattern string, re RouterElement)
Put(pattern string, re RouterElement)
Trace(pattern string, re RouterElement)
// router のエンドポイントと input, output の型定義を出力する
GetRouterDefinition() ([]*RouterDefinition, map[string]*TypeStructure)
}

type RouterDefinition struct {
InputTypeStructure *TypeStructure `json:"input_type_structure"`
OutputTypeStructure *TypeStructure `json:"output_type_structure"`
FullPathName string `json:"full_path_name"`
CurrentPathName string `json:"current_path_name"`
Method string `json:"method"`
WithAuth bool `json:"with_auth"`
}
30 changes: 30 additions & 0 deletions rapi/router_element.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package rapi

import (
"context"
"net/http"
)

type RouterElement interface {
GetHandleFunc() http.HandlerFunc
GetEmptyInput() any
GetEmptyOutput() any
}

type HandlerMethod[I any] interface {
RouterElement
// 共通のリクエストパラメーター受け取り処理をセット
SetInputFunc(func(ctx context.Context, r *http.Request, param any) error)
// 共通のバリデーション処理をセット
SetValidateFunc(func(ctx context.Context, param any) error)
// エラーをレンダリングする処理をセット
SetHandleErrorFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error))
// レスポンスをレンダリングする処理をセット
SetRenderFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request, output any))
// エラーをレンダリングする直前にエラーを書き換える処理をセット
BeforeHandleError(func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) error)
// 共通リクエストパラメーター受け取り処理の後に必要な処理があればセット
AfterInput(func(ctx context.Context, r *http.Request, param *I) error)
// 共通のバリデーション処理の後に必要な処理があればセット
AfterValidate(func(ctx context.Context, param *I) error)
}
120 changes: 120 additions & 0 deletions rapi/router_element_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package rapi

import (
"context"
"net/http"
)

func NewHandlerMethod[I, O any](f func(ctx context.Context, param *I) (*O, error)) HandlerMethod[I] {
return &handlerMethod[I, O]{
ServiceFunc: f,
}
}

type handlerMethod[I, O any] struct {
InputFunc func(ctx context.Context, r *http.Request, param any) error
AfterInputFunc func(ctx context.Context, r *http.Request, param *I) error
ValidateFunc func(ctx context.Context, param any) error
AfterValidateFunc func(ctx context.Context, param *I) error
ServiceFunc func(ctx context.Context, param *I) (*O, error)
BeforeHandleErrorFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) error
HandleErrorFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)
RenderFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, output any)
}

// --- handlerMethod implements ---

func (h *handlerMethod[I, O]) SetInputFunc(f func(ctx context.Context, r *http.Request, param any) error) {
h.InputFunc = f
}

func (h *handlerMethod[I, O]) SetValidateFunc(f func(ctx context.Context, param any) error) {
h.ValidateFunc = f
}

func (h *handlerMethod[I, O]) SetRenderFunc(f func(ctx context.Context, w http.ResponseWriter, r *http.Request, output any)) {
h.RenderFunc = f
}

func (h *handlerMethod[I, O]) SetHandleErrorFunc(f func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error)) {
h.HandleErrorFunc = f
}

func (h *handlerMethod[I, O]) BeforeHandleError(f func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) error) {
h.BeforeHandleErrorFunc = f
}

func (h *handlerMethod[I, O]) AfterInput(f func(ctx context.Context, r *http.Request, param *I) error) {
h.AfterInputFunc = f
}

func (h *handlerMethod[I, O]) AfterValidate(f func(ctx context.Context, param *I) error) {
h.AfterValidateFunc = f
}

// --- RouterElement implements ---

func (h *handlerMethod[I, O]) handleError(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) {
if h.BeforeHandleErrorFunc != nil {
err = h.BeforeHandleErrorFunc(ctx, w, r, err)
}
if h.HandleErrorFunc != nil {
h.HandleErrorFunc(ctx, w, r, err)
}
}

func (h *handlerMethod[I, O]) GetHandleFunc() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var param I
if h.InputFunc != nil {
if err := h.InputFunc(ctx, r, &param); err != nil {
h.handleError(ctx, w, r, err)
return
}
}
if h.AfterInputFunc != nil {
if err := h.AfterInputFunc(ctx, r, &param); err != nil {
h.handleError(ctx, w, r, err)
return
}
}

if h.ValidateFunc != nil {
if err := h.ValidateFunc(ctx, &param); err != nil {
h.handleError(ctx, w, r, err)
return
}
}

if h.AfterValidateFunc != nil {
if err := h.AfterValidateFunc(ctx, &param); err != nil {
h.handleError(ctx, w, r, err)
return
}
}
var output *O
var err error
if h.ServiceFunc != nil {
output, err = h.ServiceFunc(ctx, &param)
if err != nil {
h.handleError(ctx, w, r, err)
return
}
}

if h.RenderFunc == nil {
panic("RenderFunc is required")
}
h.RenderFunc(ctx, w, r, output)
}
}

func (h *handlerMethod[I, O]) GetEmptyInput() any {
return *new(I)
}

func (h *handlerMethod[I, O]) GetEmptyOutput() any {
return *new(O)
}
196 changes: 196 additions & 0 deletions rapi/router_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package rapi

import (
"net/http"

"github.com/go-chi/chi"
)

func NewRouter() Router {
r := &router{
chiRouter: chi.NewRouter(),
children: []*router{},
authMiddlewares: chi.Middlewares{},
optAuthMiddlewares: chi.Middlewares{},
}
r.root = r
return r
}

type router struct {
method string
path string
root *router
parent *router
withAuth bool
chiRouter chi.Router
element RouterElement
children []*router
authMiddlewares chi.Middlewares
optAuthMiddlewares chi.Middlewares
}

func (r *router) sub() *router {
subRouter := &router{
children: []*router{},
root: r.root,
parent: r,
}
r.children = append(r.children, subRouter)
return subRouter
}

func (r *router) handle(method string, pattern string, re RouterElement) {
subRouter := r.sub()
subRouter.chiRouter = r.chiRouter
subRouter.element = re
subRouter.path = pattern
subRouter.method = method

switch method {
case http.MethodConnect:
subRouter.chiRouter.Connect(pattern, re.GetHandleFunc())
case http.MethodDelete:
subRouter.chiRouter.Delete(pattern, re.GetHandleFunc())
case http.MethodGet:
subRouter.chiRouter.Get(pattern, re.GetHandleFunc())
case http.MethodHead:
subRouter.chiRouter.Head(pattern, re.GetHandleFunc())
case http.MethodOptions:
subRouter.chiRouter.Options(pattern, re.GetHandleFunc())
case http.MethodPatch:
subRouter.chiRouter.Patch(pattern, re.GetHandleFunc())
case http.MethodPost:
subRouter.chiRouter.Post(pattern, re.GetHandleFunc())
case http.MethodPut:
subRouter.chiRouter.Put(pattern, re.GetHandleFunc())
case http.MethodTrace:
subRouter.chiRouter.Trace(pattern, re.GetHandleFunc())
}
}

func (r *router) GetChiRouter() chi.Router {
return r.chiRouter
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.chiRouter.ServeHTTP(w, req)
}

func (r *router) Route(pattern string, fn func(r Router)) Router {
subRouter := r.sub()
subRouter.path = pattern

if fn != nil {
r.chiRouter.Route(pattern, func(chiRouter chi.Router) {
subRouter.chiRouter = chiRouter
fn(subRouter)
})
} else {
subRouter.chiRouter = r.chiRouter.Route(pattern, nil)
}

return subRouter
}

func (r *router) Use(middlewares ...func(http.Handler) http.Handler) {
r.chiRouter.Use(middlewares...)
}

func (r *router) SetAuthMiddleware(middlewares ...func(http.Handler) http.Handler) {
r.root.authMiddlewares = append(r.authMiddlewares, middlewares...)
}

func (r *router) SetOptAuthMiddleware(middlewares ...func(http.Handler) http.Handler) {
r.root.optAuthMiddlewares = append(r.optAuthMiddlewares, middlewares...)
}

func (r *router) With(middlewares ...func(http.Handler) http.Handler) Router {
return r.with(middlewares...)
}

func (r *router) with(middlewares ...func(http.Handler) http.Handler) *router {
subRouter := r.sub()
subRouter.chiRouter = r.chiRouter.With(middlewares...)
return subRouter
}

func (r *router) Auth() Router {
subRouter := r.with(r.root.authMiddlewares...)
subRouter.withAuth = true
return subRouter
}

func (r *router) OptAuth() Router {
subRouter := r.with(r.root.optAuthMiddlewares...)
subRouter.withAuth = true
return subRouter
}

func (r *router) Connect(pattern string, re RouterElement) {
r.handle(http.MethodConnect, pattern, re)
}

func (r *router) Delete(pattern string, re RouterElement) {
r.handle(http.MethodDelete, pattern, re)
}

func (r *router) Get(pattern string, re RouterElement) {
r.handle(http.MethodGet, pattern, re)
}

func (r *router) Head(pattern string, re RouterElement) {
r.handle(http.MethodHead, pattern, re)
}

func (r *router) Options(pattern string, re RouterElement) {
r.handle(http.MethodOptions, pattern, re)
}

func (r *router) Patch(pattern string, re RouterElement) {
r.handle(http.MethodPatch, pattern, re)
}

func (r *router) Post(pattern string, re RouterElement) {
r.handle(http.MethodPost, pattern, re)
}

func (r *router) Put(pattern string, re RouterElement) {
r.handle(http.MethodPut, pattern, re)
}

func (r *router) Trace(pattern string, re RouterElement) {
r.handle(http.MethodTrace, pattern, re)
}

// router のエンドポイントと input, output の型定義を出力する
func (r *router) GetRouterDefinition() ([]*RouterDefinition, map[string]*TypeStructure) {
ts := NewTypeScanner()
ts.DisableStructField()
ts.AddStructTagName("json", "form")

routerDefinitions := []*RouterDefinition{}

// 再帰で全てのRouter定義をappend
var appendRouterDefinition func(r *router, parentPath string)
appendRouterDefinition = func(r *router, parentPath string) {
if r.element != nil {
routerDefinition := &RouterDefinition{
FullPathName: parentPath + r.path,
CurrentPathName: r.path,
Method: r.method,
WithAuth: r.withAuth,
InputTypeStructure: ts.Scan(r.element.GetEmptyInput()),
OutputTypeStructure: ts.Scan(r.element.GetEmptyOutput()),
}
routerDefinitions = append(routerDefinitions, routerDefinition)
}

for _, child := range r.children {
appendRouterDefinition(child, parentPath+r.path)
}
}

appendRouterDefinition(r.root, "")
return routerDefinitions, ts.Export()
}
Loading

0 comments on commit 99f7ba9

Please sign in to comment.