API docs
This commit is contained in:
12
api/api.go
12
api/api.go
@@ -2,13 +2,17 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/docgen"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var routes = flag.Bool("routes", false, "Generate API route documentation")
|
||||||
|
|
||||||
func Start() {
|
func Start() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -40,5 +44,13 @@ func Start() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if *routes {
|
||||||
|
fmt.Println(docgen.MarkdownRoutesDoc(r, docgen.MarkdownOpts{
|
||||||
|
ProjectPath: "git.dubyatp.xyz/chat-api-server",
|
||||||
|
Intro: "Welcome to the chat API server. This is a simple API for sending and receiving messages.",
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
http.ListenAndServe(":3000", r)
|
http.ListenAndServe(":3000", r)
|
||||||
}
|
}
|
||||||
|
73
docs/routes.md
Normal file
73
docs/routes.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
## Routes
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>`/`</summary>
|
||||||
|
|
||||||
|
- [RequestID]()
|
||||||
|
- [Logger]()
|
||||||
|
- [Recoverer]()
|
||||||
|
- [URLFormat]()
|
||||||
|
- [git.dubyatp.xyz/chat-api-server/api.Start.SetContentType.func5]()
|
||||||
|
- **/**
|
||||||
|
- _GET_
|
||||||
|
- [Start.func1]()
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>`/messages`</summary>
|
||||||
|
|
||||||
|
- [RequestID]()
|
||||||
|
- [Logger]()
|
||||||
|
- [Recoverer]()
|
||||||
|
- [URLFormat]()
|
||||||
|
- [git.dubyatp.xyz/chat-api-server/api.Start.SetContentType.func5]()
|
||||||
|
- **/messages**
|
||||||
|
- **/**
|
||||||
|
- _GET_
|
||||||
|
- [ListMessages]()
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>`/messages/{messageID}`</summary>
|
||||||
|
|
||||||
|
- [RequestID]()
|
||||||
|
- [Logger]()
|
||||||
|
- [Recoverer]()
|
||||||
|
- [URLFormat]()
|
||||||
|
- [git.dubyatp.xyz/chat-api-server/api.Start.SetContentType.func5]()
|
||||||
|
- **/messages**
|
||||||
|
- **/{messageID}**
|
||||||
|
- [MessageCtx]()
|
||||||
|
- **/**
|
||||||
|
- _GET_
|
||||||
|
- [GetMessage]()
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>`/panic`</summary>
|
||||||
|
|
||||||
|
- [RequestID]()
|
||||||
|
- [Logger]()
|
||||||
|
- [Recoverer]()
|
||||||
|
- [URLFormat]()
|
||||||
|
- [git.dubyatp.xyz/chat-api-server/api.Start.SetContentType.func5]()
|
||||||
|
- **/panic**
|
||||||
|
- _GET_
|
||||||
|
- [Start.func3]()
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>`/ping`</summary>
|
||||||
|
|
||||||
|
- [RequestID]()
|
||||||
|
- [Logger]()
|
||||||
|
- [Recoverer]()
|
||||||
|
- [URLFormat]()
|
||||||
|
- [git.dubyatp.xyz/chat-api-server/api.Start.SetContentType.func5]()
|
||||||
|
- **/ping**
|
||||||
|
- _GET_
|
||||||
|
- [Start.func2]()
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Total # of routes: 5
|
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.23
|
|||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.2.0
|
github.com/go-chi/chi/v5 v5.2.0
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
|
github.com/go-chi/docgen v1.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/ajg/form v1.5.1 // indirect
|
require github.com/ajg/form v1.5.1 // indirect
|
||||||
|
6
go.sum
6
go.sum
@@ -1,6 +1,12 @@
|
|||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-chi/docgen v1.3.0 h1:dmDJ2I+EJfCTrxfgxQDwfR/OpZLTRFKe7EKB8v7yuxI=
|
||||||
|
github.com/go-chi/docgen v1.3.0/go.mod h1:G9W0G551cs2BFMSn/cnGwX+JBHEloAgo17MBhyrnhPI=
|
||||||
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
20
vendor/github.com/go-chi/docgen/LICENSE
generated
vendored
Normal file
20
vendor/github.com/go-chi/docgen/LICENSE
generated
vendored
Normal 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.
|
126
vendor/github.com/go-chi/docgen/builder.go
generated
vendored
Normal file
126
vendor/github.com/go-chi/docgen/builder.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package docgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildDoc(r chi.Routes) (Doc, error) {
|
||||||
|
d := Doc{}
|
||||||
|
|
||||||
|
goPath := getGoPath()
|
||||||
|
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(getGoPath(), "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
64
vendor/github.com/go-chi/docgen/docgen.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package docgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
116
vendor/github.com/go-chi/docgen/funcinfo.go
generated
vendored
Normal file
116
vendor/github.com/go-chi/docgen/funcinfo.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package docgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"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(getGoPath(), "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 {
|
||||||
|
value := reflect.ValueOf(i)
|
||||||
|
if value.Kind() != reflect.Func {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pc := value.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 ""
|
||||||
|
}
|
218
vendor/github.com/go-chi/docgen/markdown.go
generated
vendored
Normal file
218
vendor/github.com/go-chi/docgen/markdown.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package docgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
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, normalizer(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", normalizer(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizer(s string) string {
|
||||||
|
if strings.Contains(s, "/*") {
|
||||||
|
return strings.Replace(s, "/*", "", -1)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
50
vendor/github.com/go-chi/docgen/util.go
generated
vendored
Normal file
50
vendor/github.com/go-chi/docgen/util.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package docgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/build"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoPath() string {
|
||||||
|
goPath := os.Getenv("GOPATH")
|
||||||
|
if goPath == "" {
|
||||||
|
goPath = build.Default.GOPATH
|
||||||
|
}
|
||||||
|
return goPath
|
||||||
|
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -5,6 +5,9 @@ github.com/ajg/form
|
|||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/go-chi/chi/v5
|
github.com/go-chi/chi/v5
|
||||||
github.com/go-chi/chi/v5/middleware
|
github.com/go-chi/chi/v5/middleware
|
||||||
|
# github.com/go-chi/docgen v1.3.0
|
||||||
|
## explicit; go 1.15
|
||||||
|
github.com/go-chi/docgen
|
||||||
# github.com/go-chi/render v1.0.3
|
# github.com/go-chi/render v1.0.3
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/go-chi/render
|
github.com/go-chi/render
|
||||||
|
Reference in New Issue
Block a user