Skip to content

Commit

Permalink
code to generate API documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
bradrydzewski committed Oct 6, 2015
1 parent 25d6a8c commit 2b00d12
Show file tree
Hide file tree
Showing 7 changed files with 579 additions and 501 deletions.
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ deps:
go get -u github.com/elazarl/go-bindata-assetfs/...
go get -u github.com/dchest/jsmin
go get -u github.com/franela/goblin
go get -u github.com/go-swagger/go-swagger

gen:
go generate $(PACKAGES)
gen: gen_static gen_template gen_migrations

gen_static:
mkdir -p static/docs_gen/api
go generate github.com/drone/drone/static

gen_template:
go generate github.com/drone/drone/template

gen_migrations:
go generate github.com/drone/drone/shared/database

build:
go build
Expand Down
219 changes: 219 additions & 0 deletions contrib/generate-api-docs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// +build ignore

// This program generates api documentation from a
// swaggerfile using an amber template.

package main

import (
"crypto/md5"
"flag"
"fmt"
"io"
"log"
"os"

"github.com/eknkc/amber"
"github.com/go-swagger/go-swagger/spec"
)

var (
templ = flag.String("template", "index.amber", "")
input = flag.String("input", "swagger.json", "")
output = flag.String("output", "", "")
)

func main() {
flag.Parse()

// parses the swagger spec file
spec, err := spec.YAMLSpec(*input)
if err != nil {
log.Fatal(err)
}
swag := spec.Spec()

// create output source for file. defaults to
// stdout but may be file.
var w io.WriteCloser = os.Stdout
if *output != "" {
w, err = os.Create(*output)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}
defer w.Close()
}

// we wrap the swagger file in a map, otherwise it
// won't work with our existing templates, which expect
// a map as the root parameter.
var data = map[string]interface{}{
"Swagger": normalize(swag),
}

t := amber.MustCompileFile(*templ, amber.DefaultOptions)
err = t.Execute(w, data)
if err != nil {
log.Fatal(err)
}
}

// Swagger is a simplified representation of the swagger
// document with a subset of the fields used to generate
// our API documentation.
type Swagger struct {
Tags []Tag
}

type Tag struct {
Name string
Ops []Operation
}

type Operation struct {
ID string
Method string
Path string
Desc string
Summary string

Params []Param
Results []Result
}

type Param struct {
Name string
Desc string
Type string
Example interface{}
InputTo string
IsObject bool
}

type Result struct {
Status int
Desc string
Example interface{}
IsObject bool
IsArray bool
}

// normalize is a helper function that normalizes the swagger
// file to a simpler format that makes it easier to work with
// inside the template.
func normalize(swag *spec.Swagger) Swagger {
swag_ := Swagger{}

for _, tag := range swag.Tags {
tag_ := Tag{Name: tag.Name}

// group the paths based on their tag value.
for route, path := range swag.Paths.Paths {

var ops = []*spec.Operation{
path.Get,
path.Put,
path.Post,
path.Patch,
path.Delete,
}

// flatten the operations into an array and convert
// the underlying data so that it is a bit easier to
// work with.
for _, op := range ops {

// the operation must have a tag to
// be rendered in our custom template.
if op == nil || !hasTag(tag.Name, op.Tags) {
continue
}

item := Operation{}
item.Path = route
item.Method = getMethod(op, path)
item.Desc = op.Description
item.Summary = op.Summary
item.ID = fmt.Sprintf("%x", md5.Sum([]byte(item.Path+item.Method)))

// convert the operation input parameters into
// our internal format so that it is easier to
// work with in the template.
for _, param := range op.Parameters {
param_ := Param{}
param_.Name = param.Name
param_.Desc = param.Description
param_.Type = param.Type
param_.IsObject = param.Schema != nil
param_.InputTo = param.In

if param_.IsObject {
param_.Type = param.Schema.Ref.GetPointer().String()[13:]
param_.Example = param.Schema.Example
}
item.Params = append(item.Params, param_)
}

// convert the operation response types into
// our internal format so that it is easier to
// work with in the template.
for code, resp := range op.Responses.StatusCodeResponses {
result := Result{}
result.Desc = resp.Description
result.Status = code
result.IsObject = resp.Schema != nil
if result.IsObject {
result.IsArray = resp.Schema.Items != nil

name := resp.Schema.Ref.GetPointer().String()
if len(name) != 0 {
def, _ := swag.Definitions[name[13:]]
result.Example = def.Example
}
}
if result.IsArray {
name := resp.Schema.Items.Schema.Ref.GetPointer().String()
def, _ := swag.Definitions[name[13:]]
result.Example = def.Example
}
item.Results = append(item.Results, result)
}
tag_.Ops = append(tag_.Ops, item)
}
}

swag_.Tags = append(swag_.Tags, tag_)
}

return swag_
}

// hasTag is a helper function that returns true if
// an operation has the specified tag label.
func hasTag(want string, in []string) bool {
for _, got := range in {
if got == want {
return true
}
}
return false
}

// getMethod is a helper function that returns the http
// method for the specified operation in a path.
func getMethod(op *spec.Operation, path spec.PathItem) string {
switch {
case op == path.Get:
return "GET"
case op == path.Put:
return "PUT"
case op == path.Patch:
return "PATCH"
case op == path.Post:
return "POST"
case op == path.Delete:
return "DELETE"
}
return ""
}
Loading

0 comments on commit 2b00d12

Please sign in to comment.