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

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