adds api routes to documentation

This commit is contained in:
dhax 2017-09-28 21:58:25 +02:00
parent b36ccae974
commit fb668425de
14 changed files with 1457 additions and 2 deletions

8
Gopkg.lock generated
View file

@ -43,6 +43,12 @@
revision = "dba6525398619dead495962a916728e7ee2ca322"
version = "v1.0.0"
[[projects]]
name = "github.com/go-chi/docgen"
packages = ["."]
revision = "ac43d9a63f3b58b1e82922411d2de365d896ee72"
version = "v1.0.2"
[[projects]]
name = "github.com/go-chi/jwtauth"
packages = ["."]
@ -261,6 +267,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "7eee4771041702973c1452dd07edcb18368c463ba616225a8479676443a53faf"
inputs-digest = "4e5e0d4a6f658ef6e0fce069907b987ec1e2459bbf2b1cfe8118caf22888a05c"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -22,6 +22,12 @@ Easily extendible RESTful API boilerplate aiming to follow idiomatic go and best
- Run all migrations found in ./database/migrate with: ```go-base migrate```
- Run the application: ```go-base serve```
Run ```go-base``` only to see the cobra generated help message.
### API Routes
Check [routes.md](routes.md) file for an overview of the provided API routes.
#### Demo client application
For demonstration of the login and account management features this API also serves a Single Page Application (SPA) as a Progressive Web App (PWA) done with [Quasar Framework](http://quasar-framework.org) which itself is powered by [Vue.js](https://vuejs.org). The client's source code can be found [here](https://github.com/dhax/go-base-client).
@ -51,4 +57,3 @@ EMAIL_SMTP_USER | string || email smtp username
EMAIL_SMTP_PASSWORD | string || email smtp password
EMAIL_FROM_ADDRESS | string || from address used in sending emails
EMAIL_FROM_NAME | string || from name used in sending emails

72
cmd/gendoc.go Normal file
View file

@ -0,0 +1,72 @@
// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"io/ioutil"
"github.com/dhax/go-base/api"
"github.com/go-chi/docgen"
"github.com/spf13/cobra"
)
var (
routes bool
)
// gendocCmd represents the gendoc command
var gendocCmd = &cobra.Command{
Use: "gendoc",
Short: "Generate project documentation",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
if routes {
genRoutesDoc()
}
},
}
func init() {
RootCmd.AddCommand(gendocCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// gendocCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// gendocCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
gendocCmd.Flags().BoolVarP(&routes, "routes", "r", false, "create api routes markdown file")
}
func genRoutesDoc() {
fmt.Println("generating routes markdown file")
api, _ := api.NewAPI()
md := docgen.MarkdownRoutesDoc(api, docgen.MarkdownOpts{
ProjectPath: "github.com/dhax/go-base",
Intro: "GoBase REST API.",
})
if err := ioutil.WriteFile("routes.md", []byte(md), 0644); err != nil {
fmt.Println(err)
}
}

220
routes.md Normal file
View file

@ -0,0 +1,220 @@
# github.com/dhax/go-base
GoBase REST API.
## Routes
<details>
<summary>`/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/***
- _GET_
- [SPAHandler.func1](/api/api.go#L101)
</details>
<details>
<summary>`/admin/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/admin/***
- [RequiresRole.func1](/auth/authorizer.go#L11)
- **/**
- _GET_
- [(*API).Router.func1](/api/admin/api.go#L42)
</details>
<details>
<summary>`/admin/*/accounts/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/admin/***
- [RequiresRole.func1](/auth/authorizer.go#L11)
- **/accounts/***
- **/**
- _GET_
- [(*AccountResource).(github.com/dhax/go-base/api/admin.list)-fm](/api/admin/accounts.go#L50)
- _POST_
- [(*AccountResource).(github.com/dhax/go-base/api/admin.create)-fm](/api/admin/accounts.go#L51)
</details>
<details>
<summary>`/admin/*/accounts/*/{accountID}/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/admin/***
- [RequiresRole.func1](/auth/authorizer.go#L11)
- **/accounts/***
- **/{accountID}/***
- [(*AccountResource).(github.com/dhax/go-base/api/admin.accountCtx)-fm](/api/admin/accounts.go#L53)
- **/**
- _PUT_
- [(*AccountResource).(github.com/dhax/go-base/api/admin.update)-fm](/api/admin/accounts.go#L55)
- _DELETE_
- [(*AccountResource).(github.com/dhax/go-base/api/admin.delete)-fm](/api/admin/accounts.go#L56)
- _GET_
- [(*AccountResource).(github.com/dhax/go-base/api/admin.get)-fm](/api/admin/accounts.go#L54)
</details>
<details>
<summary>`/api/*/account/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/api/***
- **/account/***
- [(*AccountResource).(github.com/dhax/go-base/api/app.accountCtx)-fm](/api/app/account.go#L48)
- **/**
- _PUT_
- [(*AccountResource).(github.com/dhax/go-base/api/app.update)-fm](/api/app/account.go#L50)
- _DELETE_
- [(*AccountResource).(github.com/dhax/go-base/api/app.delete)-fm](/api/app/account.go#L51)
- _GET_
- [(*AccountResource).(github.com/dhax/go-base/api/app.get)-fm](/api/app/account.go#L49)
</details>
<details>
<summary>`/api/*/account/*/profile`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/api/***
- **/account/***
- [(*AccountResource).(github.com/dhax/go-base/api/app.accountCtx)-fm](/api/app/account.go#L48)
- **/profile**
- _PUT_
- [(*AccountResource).(github.com/dhax/go-base/api/app.updateProfile)-fm](/api/app/account.go#L56)
</details>
<details>
<summary>`/api/*/account/*/token/{tokenID}/*`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/api/***
- **/account/***
- [(*AccountResource).(github.com/dhax/go-base/api/app.accountCtx)-fm](/api/app/account.go#L48)
- **/token/{tokenID}/***
- **/**
- _PUT_
- [(*AccountResource).(github.com/dhax/go-base/api/app.updateToken)-fm](/api/app/account.go#L53)
- _DELETE_
- [(*AccountResource).(github.com/dhax/go-base/api/app.deleteToken)-fm](/api/app/account.go#L54)
</details>
<details>
<summary>`/auth/*/login`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/auth/***
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/login**
- _POST_
- [(*Resource).(github.com/dhax/go-base/auth.login)-fm](/auth/api.go#L67)
</details>
<details>
<summary>`/auth/*/logout`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/auth/***
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/logout**
- _POST_
- [Verifier.func1](/vendor/github.com/go-chi/jwtauth/jwtauth.go#L70)
- [AuthenticateRefreshJWT](/auth/authenticator.go#L66)
- [(*Resource).(github.com/dhax/go-base/auth.logout)-fm](/auth/api.go#L73)
</details>
<details>
<summary>`/auth/*/refresh`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/auth/***
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/refresh**
- _POST_
- [Verifier.func1](/vendor/github.com/go-chi/jwtauth/jwtauth.go#L70)
- [AuthenticateRefreshJWT](/auth/authenticator.go#L66)
- [(*Resource).(github.com/dhax/go-base/auth.refresh)-fm](/auth/api.go#L72)
</details>
<details>
<summary>`/auth/*/token`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/auth/***
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/token**
- _POST_
- [(*Resource).(github.com/dhax/go-base/auth.token)-fm](/auth/api.go#L68)
</details>
<details>
<summary>`/ping`</summary>
- [Recoverer](/vendor/github.com/go-chi/chi/middleware/recoverer.go#L18)
- [RequestID](/vendor/github.com/go-chi/chi/middleware/request_id.go#L63)
- [DefaultCompress](/vendor/github.com/go-chi/chi/middleware/compress.go#L38)
- [Timeout.func1](/vendor/github.com/go-chi/chi/middleware/timeout.go#L33)
- [RequestLogger.func1](/vendor/github.com/go-chi/chi/middleware/logger.go#L36)
- [SetContentType.func1](/vendor/github.com/go-chi/render/content_type.go#L49)
- **/ping**
- _GET_
- [NewAPI.func2](/api/api.go#L73)
</details>
Total # of routes: 12

15
vendor/github.com/go-chi/docgen/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,15 @@
language: go
go:
- 1.7.x
- 1.8.x
- tip
install:
- go get -u golang.org/x/tools/cmd/goimports
script:
- go get -d -t ./...
- go test ./...
- >
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :

20
vendor/github.com/go-chi/docgen/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2016-Present https://github.com/go-chi authors
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

127
vendor/github.com/go-chi/docgen/builder.go generated vendored Normal file
View file

@ -0,0 +1,127 @@
package docgen
import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-chi/chi"
)
func BuildDoc(r chi.Routes) (Doc, error) {
d := Doc{}
goPath := os.Getenv("GOPATH")
if goPath == "" {
return d, errors.New("docgen: unable to determine your $GOPATH")
}
// Walk and generate the router docs
d.Router = buildDocRouter(r)
return d, nil
}
func buildDocRouter(r chi.Routes) DocRouter {
rts := r
dr := DocRouter{Middlewares: []DocMiddleware{}}
drts := DocRoutes{}
dr.Routes = drts
for _, mw := range rts.Middlewares() {
dmw := DocMiddleware{
FuncInfo: buildFuncInfo(mw),
}
dr.Middlewares = append(dr.Middlewares, dmw)
}
for _, rt := range rts.Routes() {
drt := DocRoute{Pattern: rt.Pattern, Handlers: DocHandlers{}}
if rt.SubRoutes != nil {
subRoutes := rt.SubRoutes
subDrts := buildDocRouter(subRoutes)
drt.Router = &subDrts
} else {
hall := rt.Handlers["*"]
for method, h := range rt.Handlers {
if method != "*" && hall != nil && fmt.Sprintf("%v", hall) == fmt.Sprintf("%v", h) {
continue
}
dh := DocHandler{Method: method, Middlewares: []DocMiddleware{}}
var endpoint http.Handler
chain, _ := h.(*chi.ChainHandler)
if chain != nil {
for _, mw := range chain.Middlewares {
dh.Middlewares = append(dh.Middlewares, DocMiddleware{
FuncInfo: buildFuncInfo(mw),
})
}
endpoint = chain.Endpoint
} else {
endpoint = h
}
dh.FuncInfo = buildFuncInfo(endpoint)
drt.Handlers[method] = dh
}
}
drts[rt.Pattern] = drt
}
return dr
}
func buildFuncInfo(i interface{}) FuncInfo {
fi := FuncInfo{}
frame := getCallerFrame(i)
goPathSrc := filepath.Join(os.Getenv("GOPATH"), "src")
if frame == nil {
fi.Unresolvable = true
return fi
}
pkgName := getPkgName(frame.File)
if pkgName == "chi" {
fi.Unresolvable = true
}
funcPath := frame.Func.Name()
idx := strings.Index(funcPath, "/"+pkgName)
if idx > 0 {
fi.Pkg = funcPath[:idx+1+len(pkgName)]
fi.Func = funcPath[idx+2+len(pkgName):]
} else {
fi.Func = funcPath
}
if strings.Index(fi.Func, ".func") > 0 {
fi.Anonymous = true
}
fi.File = frame.File
fi.Line = frame.Line
if filepath.HasPrefix(fi.File, goPathSrc) {
fi.File = fi.File[len(goPathSrc)+1:]
}
// Check if file info is unresolvable
if !strings.Contains(funcPath, pkgName) {
fi.Unresolvable = true
}
if !fi.Unresolvable {
fi.Comment = getFuncComment(frame.File, frame.Line)
}
return fi
}

64
vendor/github.com/go-chi/docgen/docgen.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
package docgen
import (
"encoding/json"
"fmt"
"github.com/go-chi/chi"
)
type Doc struct {
Router DocRouter `json:"router"`
}
type DocRouter struct {
Middlewares []DocMiddleware `json:"middlewares"`
Routes DocRoutes `json:"routes"`
}
type DocMiddleware struct {
FuncInfo
}
type DocRoute struct {
Pattern string `json:"-"`
Handlers DocHandlers `json:"handlers,omitempty"`
Router *DocRouter `json:"router,omitempty"`
}
type DocRoutes map[string]DocRoute // Pattern : DocRoute
type DocHandler struct {
Middlewares []DocMiddleware `json:"middlewares"`
Method string `json:"method"`
FuncInfo
}
type DocHandlers map[string]DocHandler // Method : DocHandler
func PrintRoutes(r chi.Routes) {
var printRoutes func(parentPattern string, r chi.Routes)
printRoutes = func(parentPattern string, r chi.Routes) {
rts := r.Routes()
for _, rt := range rts {
if rt.SubRoutes == nil {
fmt.Println(parentPattern + rt.Pattern)
} else {
pat := rt.Pattern
subRoutes := rt.SubRoutes
printRoutes(parentPattern+pat, subRoutes)
}
}
}
printRoutes("", r)
}
func JSONRoutesDoc(r chi.Routes) string {
doc, _ := BuildDoc(r)
v, err := json.MarshalIndent(doc, "", " ")
if err != nil {
panic(err)
}
return string(v)
}

181
vendor/github.com/go-chi/docgen/docgen_test.go generated vendored Normal file
View file

@ -0,0 +1,181 @@
package docgen_test
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/go-chi/chi"
"github.com/go-chi/docgen"
)
// RequestID comment goes here.
func RequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "requestID", "1")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func hubIndexHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s reqid:%s session:%s",
chi.URLParam(r, "hubID"), ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
}
// Generate docs for the MuxBig from chi/mux_test.go
func TestMuxBig(t *testing.T) {
var r, sr1, sr2, sr3, sr4, sr5, sr6 *chi.Mux
r = chi.NewRouter()
r.Use(RequestID)
// Some inline middleware, 1
// We just love Go's ast tools
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
})
r.Group(func(r chi.Router) {
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "session.user", "anonymous")
next.ServeHTTP(w, r.WithContext(ctx))
})
})
r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("fav"))
})
r.Get("/hubs/{hubID}/view", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/view reqid:%s session:%s", chi.URLParam(r, "hubID"),
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
r.Get("/hubs/{hubID}/view/*", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/view/%s reqid:%s session:%s", chi.URLParamFromCtx(ctx, "hubID"),
chi.URLParam(r, "*"), ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
})
r.Group(func(r chi.Router) {
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "session.user", "elvis")
next.ServeHTTP(w, r.WithContext(ctx))
})
})
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/ reqid:%s session:%s", ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
r.Get("/suggestions", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/suggestions reqid:%s session:%s", ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
r.Get("/woot/{wootID}/*", func(w http.ResponseWriter, r *http.Request) {
s := fmt.Sprintf("/woot/%s/%s", chi.URLParam(r, "wootID"), chi.URLParam(r, "*"))
w.Write([]byte(s))
})
r.Route("/hubs", func(r chi.Router) {
sr1 = r.(*chi.Mux)
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
})
r.Route("/{hubID}", func(r chi.Router) {
sr2 = r.(*chi.Mux)
r.Get("/", hubIndexHandler)
r.Get("/touch", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/touch reqid:%s session:%s", chi.URLParam(r, "hubID"),
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
sr3 = chi.NewRouter()
sr3.Get("/", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/webhooks reqid:%s session:%s", chi.URLParam(r, "hubID"),
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
sr3.Route("/{webhookID}", func(r chi.Router) {
sr4 = r.(*chi.Mux)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/webhooks/%s reqid:%s session:%s", chi.URLParam(r, "hubID"),
chi.URLParam(r, "webhookID"), ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
})
// TODO: /webooks is not coming up as a subrouter here...
// we kind of want to wrap a Router... ?
// perhaps add .Router() to the middleware inline thing..
// and use that always.. or, can detect in that method..
r.Mount("/webhooks", chi.Chain(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "hook", true)))
})
}).Handler(sr3))
// HMMMM.. only let Mount() for just a Router..?
// r.Mount("/webhooks", Use(...).Router(sr3))
// ... could this work even....?
// HMMMMMMMMMMMMMMMMMMMMMMMM...
// even if Mount() were to record all subhandlers mounted, we still couldn't get at the
// routes
r.Route("/posts", func(r chi.Router) {
sr5 = r.(*chi.Mux)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/hubs/%s/posts reqid:%s session:%s", chi.URLParam(r, "hubID"),
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
})
})
})
r.Route("/folders/", func(r chi.Router) {
sr6 = r.(*chi.Mux)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/folders/ reqid:%s session:%s",
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
r.Get("/public", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := fmt.Sprintf("/folders/public reqid:%s session:%s",
ctx.Value("requestID"), ctx.Value("session.user"))
w.Write([]byte(s))
})
r.Get("/in", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}).ServeHTTP)
r.With(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "search", true)))
})
}).Get("/search", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("searching.."))
})
})
})
fmt.Println(docgen.JSONRoutesDoc(r))
// docgen.PrintRoutes(r)
}

113
vendor/github.com/go-chi/docgen/funcinfo.go generated vendored Normal file
View file

@ -0,0 +1,113 @@
package docgen
import (
"go/parser"
"go/token"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
)
type FuncInfo struct {
Pkg string `json:"pkg"`
Func string `json:"func"`
Comment string `json:"comment"`
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
Anonymous bool `json:"anonymous,omitempty"`
Unresolvable bool `json:"unresolvable,omitempty"`
}
func GetFuncInfo(i interface{}) FuncInfo {
fi := FuncInfo{}
frame := getCallerFrame(i)
goPathSrc := filepath.Join(os.Getenv("GOPATH"), "src")
if frame == nil {
fi.Unresolvable = true
return fi
}
pkgName := getPkgName(frame.File)
if pkgName == "chi" {
fi.Unresolvable = true
}
funcPath := frame.Func.Name()
idx := strings.Index(funcPath, "/"+pkgName)
if idx > 0 {
fi.Pkg = funcPath[:idx+1+len(pkgName)]
fi.Func = funcPath[idx+2+len(pkgName):]
} else {
fi.Func = funcPath
}
if strings.Index(fi.Func, ".func") > 0 {
fi.Anonymous = true
}
fi.File = frame.File
fi.Line = frame.Line
if filepath.HasPrefix(fi.File, goPathSrc) {
fi.File = fi.File[len(goPathSrc)+1:]
}
// Check if file info is unresolvable
if strings.Index(funcPath, pkgName) < 0 {
fi.Unresolvable = true
}
if !fi.Unresolvable {
fi.Comment = getFuncComment(frame.File, frame.Line)
}
return fi
}
func getCallerFrame(i interface{}) *runtime.Frame {
pc := reflect.ValueOf(i).Pointer()
frames := runtime.CallersFrames([]uintptr{pc})
if frames == nil {
return nil
}
frame, _ := frames.Next()
if frame.Entry == 0 {
return nil
}
return &frame
}
func getPkgName(file string) string {
fset := token.NewFileSet()
astFile, err := parser.ParseFile(fset, file, nil, parser.PackageClauseOnly)
if err != nil {
return ""
}
if astFile.Name == nil {
return ""
}
return astFile.Name.Name
}
func getFuncComment(file string, line int) string {
fset := token.NewFileSet()
astFile, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
if err != nil {
return ""
}
if len(astFile.Comments) == 0 {
return ""
}
for _, cmt := range astFile.Comments {
if fset.Position(cmt.End()).Line+1 == line {
return cmt.Text()
}
}
return ""
}

211
vendor/github.com/go-chi/docgen/markdown.go generated vendored Normal file
View file

@ -0,0 +1,211 @@
package docgen
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
"github.com/go-chi/chi"
)
type MarkdownDoc struct {
Opts MarkdownOpts
Router chi.Router
Doc Doc
Routes map[string]DocRouter // Pattern : DocRouter
buf *bytes.Buffer
}
type MarkdownOpts struct {
// ProjectPath is the base Go import path of the project
ProjectPath string
// Intro text included at the top of the generated markdown file.
Intro string
// ForceRelativeLinks to be relative even if they're not on github
ForceRelativeLinks bool
// URLMap allows specifying a map of package import paths to their link sources
// Used for mapping vendored dependencies to their upstream sources
// For example:
// map[string]string{"github.com/my/package/vendor/go-chi/chi/": "https://github.com/go-chi/chi/blob/master/"}
URLMap map[string]string
}
func MarkdownRoutesDoc(r chi.Router, opts MarkdownOpts) string {
md := &MarkdownDoc{Router: r, Opts: opts}
if err := md.Generate(); err != nil {
return fmt.Sprintf("ERROR: %s\n", err.Error())
}
return md.String()
}
func (md *MarkdownDoc) String() string {
return md.buf.String()
}
func (md *MarkdownDoc) Generate() error {
if md.Router == nil {
return errors.New("docgen: router is nil")
}
doc, err := BuildDoc(md.Router)
if err != nil {
return err
}
md.Doc = doc
md.buf = &bytes.Buffer{}
md.Routes = make(map[string]DocRouter)
md.WriteIntro()
md.WriteRoutes()
return nil
}
func (md *MarkdownDoc) WriteIntro() {
pkgName := md.Opts.ProjectPath
md.buf.WriteString(fmt.Sprintf("# %s\n\n", pkgName))
intro := md.Opts.Intro
md.buf.WriteString(fmt.Sprintf("%s\n\n", intro))
}
func (md *MarkdownDoc) WriteRoutes() {
md.buf.WriteString(fmt.Sprintf("## Routes\n\n"))
var buildRoutesMap func(parentPattern string, ar, nr, dr *DocRouter)
buildRoutesMap = func(parentPattern string, ar, nr, dr *DocRouter) {
nr.Middlewares = append(nr.Middlewares, dr.Middlewares...)
for pat, rt := range dr.Routes {
pattern := parentPattern + pat
nr.Routes = DocRoutes{}
if rt.Router != nil {
nnr := &DocRouter{}
nr.Routes[pat] = DocRoute{
Pattern: pat,
Handlers: rt.Handlers,
Router: nnr,
}
buildRoutesMap(pattern, ar, nnr, rt.Router)
} else if len(rt.Handlers) > 0 {
nr.Routes[pat] = DocRoute{
Pattern: pat,
Handlers: rt.Handlers,
Router: nil,
}
// Remove the trailing slash if the handler is a subroute for "/"
routeKey := pattern
if pat == "/" && len(routeKey) > 1 {
routeKey = routeKey[:len(routeKey)-1]
}
md.Routes[routeKey] = copyDocRouter(*ar)
} else {
panic("not possible")
}
}
}
// Build a route tree that consists of the full route pattern
// and the part of the tree for just that specific route, stored
// in routes map on the markdown struct. This is the structure we
// are going to render to markdown.
dr := md.Doc.Router
ar := DocRouter{}
buildRoutesMap("", &ar, &ar, &dr)
// Generate the markdown to render the above structure
var printRouter func(depth int, dr DocRouter)
printRouter = func(depth int, dr DocRouter) {
tabs := ""
for i := 0; i < depth; i++ {
tabs += "\t"
}
// Middlewares
for _, mw := range dr.Middlewares {
md.buf.WriteString(fmt.Sprintf("%s- [%s](%s)\n", tabs, mw.Func, md.githubSourceURL(mw.File, mw.Line)))
}
// Routes
for _, rt := range dr.Routes {
md.buf.WriteString(fmt.Sprintf("%s- **%s**\n", tabs, rt.Pattern))
if rt.Router != nil {
printRouter(depth+1, *rt.Router)
} else {
for meth, dh := range rt.Handlers {
md.buf.WriteString(fmt.Sprintf("%s\t- _%s_\n", tabs, meth))
// Handler middlewares
for _, mw := range dh.Middlewares {
md.buf.WriteString(fmt.Sprintf("%s\t\t- [%s](%s)\n", tabs, mw.Func, md.githubSourceURL(mw.File, mw.Line)))
}
// Handler endpoint
md.buf.WriteString(fmt.Sprintf("%s\t\t- [%s](%s)\n", tabs, dh.Func, md.githubSourceURL(dh.File, dh.Line)))
}
}
}
}
routePaths := []string{}
for pat := range md.Routes {
routePaths = append(routePaths, pat)
}
sort.Strings(routePaths)
for _, pat := range routePaths {
dr := md.Routes[pat]
md.buf.WriteString(fmt.Sprintf("<details>\n"))
md.buf.WriteString(fmt.Sprintf("<summary>`%s`</summary>\n", pat))
md.buf.WriteString(fmt.Sprintf("\n"))
printRouter(0, dr)
md.buf.WriteString(fmt.Sprintf("\n"))
md.buf.WriteString(fmt.Sprintf("</details>\n"))
}
md.buf.WriteString(fmt.Sprintf("\n"))
md.buf.WriteString(fmt.Sprintf("Total # of routes: %d\n", len(md.Routes)))
// TODO: total number of handlers..
}
func (md *MarkdownDoc) githubSourceURL(file string, line int) string {
// Currently, we only automatically link to source for github projects
if strings.Index(file, "github.com/") != 0 && !md.Opts.ForceRelativeLinks {
return ""
}
if md.Opts.ProjectPath == "" {
return ""
}
for pkg, url := range md.Opts.URLMap {
if idx := strings.Index(file, pkg); idx >= 0 {
pos := idx + len(pkg)
url = strings.TrimRight(url, "/")
filepath := strings.TrimLeft(file[pos:], "/")
return fmt.Sprintf("%s/%s#L%d", url, filepath, line)
}
}
if idx := strings.Index(file, md.Opts.ProjectPath); idx >= 0 {
// relative
pos := idx + len(md.Opts.ProjectPath)
return fmt.Sprintf("%s#L%d", file[pos:], line)
}
// absolute
return fmt.Sprintf("https://%s#L%d", file, line)
}

159
vendor/github.com/go-chi/docgen/raml/raml.go generated vendored Normal file
View file

@ -0,0 +1,159 @@
package raml
import (
"errors"
"fmt"
"strings"
yaml "gopkg.in/yaml.v2"
)
var header = `#%RAML 1.0
---
`
type RAML struct {
Title string `yaml:"title,omitempty"`
BaseUri string `yaml:"baseUri,omitempty"`
Protocols []string `yaml:"protocols,omitempty"`
MediaType string `yaml:"mediaType,omitempty"`
Version string `yaml:"version,omitempty"`
Documentation []Documentation `yaml:"documentation,omitempty"`
Resources `yaml:",inline"`
}
func (r *RAML) String() string {
bytes, _ := yaml.Marshal(r)
return fmt.Sprintf("%s%s", header, bytes)
}
type Documentation struct {
Title string `yaml:"title"`
Content string `yaml:"content"`
}
type Resources map[string]*Resource
type Resource struct {
DisplayName string `yaml:"displayName,omitempty"`
Description string `yaml:"description,omitempty"`
Responses Responses `yaml:"responses,omitempty"`
Body Body `yaml:"body,omitempty"`
Is []string `yaml:"is,omitempty"`
Type string `yaml:"type,omitempty"`
SecuredBy []string `yaml:"securedBy,omitempty"`
UriParameters []string `yaml:"uirParameters,omitempty"`
QueryParameters []string `yaml:"queryParameters,omitempty"`
Resources `yaml:",inline"`
}
type Responses map[int]Response
type Response struct {
Body `yaml:"body,omitempty"`
}
type Body map[string]Example // Content-Type to Example
type Example struct {
Example string `yaml:"example,omitempty"`
}
func (r *RAML) Add(method string, route string, resource *Resource) error {
if resource == nil {
return errors.New("raml.Add(): resource can't be nil")
}
if r.Resources == nil {
r.Resources = Resources{}
}
return r.Resources.upsert(method, route, resource)
}
func (r *RAML) AddUnder(parentRoute string, method string, route string, resource *Resource) error {
if resource == nil {
return errors.New("raml.Add(): resource can't be nil")
}
if r.Resources == nil {
r.Resources = Resources{}
}
if parentRoute == "" || parentRoute == "/" {
return errors.New("raml.AddUnderParent(): parentRoute can't be empty or '/'")
}
if !strings.HasPrefix(route, parentRoute) {
return errors.New("raml.AddUnderParent(): parentRoute must be present in the route string")
}
route = strings.TrimPrefix(route, parentRoute)
if route == "" {
route = "/"
}
parentNode, found := r.Resources[parentRoute]
if !found {
parentNode = &Resource{
Resources: Resources{},
Responses: Responses{},
}
r.Resources[parentRoute] = parentNode
}
return parentNode.Resources.upsert(method, route, resource)
}
// Find or create node tree from a given route and inject the resource.
func (r Resources) upsert(method string, route string, resource *Resource) error {
currentNode := r
parts := strings.Split(route, "/")
if len(parts) > 0 {
last := len(parts) - 1
// Upsert route of the resource.
for _, part := range parts[:last] {
if part == "" {
continue
}
part = "/" + part
node, found := currentNode[part]
if !found {
node = &Resource{
Resources: Resources{},
Responses: Responses{},
}
currentNode[part] = node
}
currentNode = node.Resources
}
if parts[last] != "" {
// Upsert resource into the very bottom of the node tree.
part := "/" + parts[last]
node, found := currentNode[part]
if !found {
node = &Resource{
Resources: Resources{},
Responses: Responses{},
}
}
currentNode[part] = node
currentNode = node.Resources
}
}
method = strings.ToLower(method)
if _, found := currentNode[method]; found {
return nil
// return fmt.Errorf("duplicated method route: %v %v", method, route)
}
currentNode[method] = resource
return nil
}

225
vendor/github.com/go-chi/docgen/raml/raml_test.go generated vendored Normal file
View file

@ -0,0 +1,225 @@
package raml_test
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"testing"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/go-chi/docgen"
"github.com/go-chi/docgen/raml"
"github.com/go-chi/render"
yaml "gopkg.in/yaml.v2"
)
func TestWalkerRAML(t *testing.T) {
r := Router()
ramlDocs := &raml.RAML{
Title: "Big Mux",
BaseUri: "https://bigmux.example.com",
Version: "v1.0",
MediaType: "application/json",
}
if err := chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
handlerInfo := docgen.GetFuncInfo(handler)
resource := &raml.Resource{
Description: handlerInfo.Comment,
}
return ramlDocs.Add(method, route, resource)
}); err != nil {
t.Error(err)
}
_, err := yaml.Marshal(ramlDocs)
if err != nil {
t.Error(err)
}
}
// Copy-pasted from _examples/raml. We can't simply import it, since it's main pkg.
func Router() chi.Router {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("root."))
})
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("test")
})
// RESTy routes for "articles" resource
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", ListArticles)
r.Post("/", CreateArticle) // POST /articles
r.Get("/search", SearchArticles) // GET /articles/search
r.Route("/:articleID", func(r chi.Router) {
r.Use(ArticleCtx) // Load the *Article on the request context
r.Get("/", GetArticle) // GET /articles/123
r.Put("/", UpdateArticle) // PUT /articles/123
r.Delete("/", DeleteArticle) // DELETE /articles/123
})
})
// Mount the admin sub-router, the same as a call to
// Route("/admin", func(r chi.Router) { with routes here })
r.Mount("/admin", adminRouter())
return r
}
type Article struct {
ID string `json:"id"`
Title string `json:"title"`
}
// Article fixture data
var articles = []*Article{
{ID: "1", Title: "Hi"},
{ID: "2", Title: "sup"},
}
// ArticleCtx middleware is used to load an Article object from
// the URL parameters passed through as the request. In case
// the Article could not be found, we stop here and return a 404.
func ArticleCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
articleID := chi.URLParam(r, "articleID")
article, err := dbGetArticle(articleID)
if err != nil {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, http.StatusText(http.StatusNotFound))
return
}
ctx := context.WithValue(r.Context(), "article", article)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Search Articles.
// Searches the Articles data for a matching article.
// It's just a stub, but you get the idea.
func SearchArticles(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, articles)
}
// List Articles.
// Returns an array of Articles.
func ListArticles(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, articles)
}
// Create new Article.
// Ppersists the posted Article and returns it
// back to the client as an acknowledgement.
func CreateArticle(w http.ResponseWriter, r *http.Request) {
article := &Article{}
render.JSON(w, r, article)
}
// Get a specific Article.
func GetArticle(w http.ResponseWriter, r *http.Request) {
article := r.Context().Value("article").(*Article)
render.JSON(w, r, article)
}
// Update a specific Article.
// Updates an existing Article in our persistent store.
func UpdateArticle(w http.ResponseWriter, r *http.Request) {
article := r.Context().Value("article").(*Article)
render.JSON(w, r, article)
}
// Delete a specific Article.
// Removes an existing Article from our persistent store.
func DeleteArticle(w http.ResponseWriter, r *http.Request) {
article := r.Context().Value("article").(*Article)
render.JSON(w, r, article)
}
// A completely separate router for administrator routes
func adminRouter() chi.Router {
r := chi.NewRouter()
r.Use(AdminOnly)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("admin: index"))
})
r.Get("/accounts", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("admin: list accounts.."))
})
r.Get("/users/:userId", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf("admin: view user id %v", chi.URLParam(r, "userId"))))
})
return r
}
// AdminOnly middleware restricts access to just administrators.
func AdminOnly(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
isAdmin, ok := r.Context().Value("acl.admin").(bool)
if !ok || !isAdmin {
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
// paginate is a stub, but very possible to implement middleware logic
// to handle the request params for handling a paginated request.
func paginate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// just a stub.. some ideas are to look at URL query params for something like
// the page number, or the limit, and send a query cursor down the chain
next.ServeHTTP(w, r)
})
}
//--
// Below are a bunch of helper functions that mock some kind of storage
func dbNewArticle(article *Article) (string, error) {
article.ID = fmt.Sprintf("%d", rand.Intn(100)+10)
articles = append(articles, article)
return article.ID, nil
}
func dbGetArticle(id string) (*Article, error) {
for _, a := range articles {
if a.ID == id {
return a, nil
}
}
return nil, errors.New("article not found.")
}
func dbRemoveArticle(id string) (*Article, error) {
for i, a := range articles {
if a.ID == id {
articles = append((articles)[:i], (articles)[i+1:]...)
return a, nil
}
}
return nil, errors.New("article not found.")
}

37
vendor/github.com/go-chi/docgen/util.go generated vendored Normal file
View file

@ -0,0 +1,37 @@
package docgen
func copyDocRouter(dr DocRouter) DocRouter {
var cloneRouter func(dr DocRouter) DocRouter
var cloneRoutes func(drt DocRoutes) DocRoutes
cloneRoutes = func(drts DocRoutes) DocRoutes {
rts := DocRoutes{}
for pat, drt := range drts {
rt := DocRoute{Pattern: drt.Pattern}
if len(drt.Handlers) > 0 {
rt.Handlers = DocHandlers{}
for meth, dh := range drt.Handlers {
rt.Handlers[meth] = dh
}
}
if drt.Router != nil {
rr := cloneRouter(*drt.Router)
rt.Router = &rr
}
rts[pat] = rt
}
return rts
}
cloneRouter = func(dr DocRouter) DocRouter {
cr := DocRouter{}
cr.Middlewares = make([]DocMiddleware, len(dr.Middlewares))
copy(cr.Middlewares, dr.Middlewares)
cr.Routes = cloneRoutes(dr.Routes)
return cr
}
return cloneRouter(dr)
}