adds api routes to documentation

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

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)
}