-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #24 from rabee-inc/feature/rapi
routing , handler を簡単に書けるライブラリ実装
- Loading branch information
Showing
8 changed files
with
698 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¶m); err != nil { | ||
h.handleError(ctx, w, r, err) | ||
return | ||
} | ||
} | ||
if h.AfterInputFunc != nil { | ||
if err := h.AfterInputFunc(ctx, r, ¶m); err != nil { | ||
h.handleError(ctx, w, r, err) | ||
return | ||
} | ||
} | ||
|
||
if h.ValidateFunc != nil { | ||
if err := h.ValidateFunc(ctx, ¶m); err != nil { | ||
h.handleError(ctx, w, r, err) | ||
return | ||
} | ||
} | ||
|
||
if h.AfterValidateFunc != nil { | ||
if err := h.AfterValidateFunc(ctx, ¶m); err != nil { | ||
h.handleError(ctx, w, r, err) | ||
return | ||
} | ||
} | ||
var output *O | ||
var err error | ||
if h.ServiceFunc != nil { | ||
output, err = h.ServiceFunc(ctx, ¶m) | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.