adds api routes to documentation
This commit is contained in:
parent
b36ccae974
commit
38722c9da5
14 changed files with 1459 additions and 2 deletions
159
vendor/github.com/go-chi/docgen/raml/raml.go
generated
vendored
Normal file
159
vendor/github.com/go-chi/docgen/raml/raml.go
generated
vendored
Normal 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
225
vendor/github.com/go-chi/docgen/raml/raml_test.go
generated
vendored
Normal 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.")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue