diff --git a/_example/doc/apib/validate.apib b/_example/doc/apib/validate.apib new file mode 100644 index 0000000..58431f2 --- /dev/null +++ b/_example/doc/apib/validate.apib @@ -0,0 +1,41 @@ +# Example API (with validation) + +# POST /v1/user +Create a new user ++ Name - User Name ++ Email - User email address ++ Attribute.Birthday - User birthday YYYY-MM-DD format + + ++ Parameters + + pretty - Pretty print response message + + token - Request token + ++ Request (application/json) + + + Headers + X-Version: 2 + + + + Body + { + "name": "tcnksm", + "email": "tcnksm@mercari.com", + "attribute": { + "birthday": "1988-11-24" + } +} + ++ Response 200 + + + Headers + Content-Type: application/json + + + + Body + { + "id": 11241988, + "name": "tcnksm" +} + + diff --git a/_example/handler_validate_test.go b/_example/handler_validate_test.go index 9b860c0..318a0cd 100644 --- a/_example/handler_validate_test.go +++ b/_example/handler_validate_test.go @@ -19,6 +19,12 @@ func TestUserHandlerWithValidate(t *testing.T) { if err := document.Generate("doc/validate.md"); err != nil { t.Fatalf("err: %s", err) } + + // This is still experimental. + document.Template = httpdoc.ExperimentalTmplAPIBlueprint + if err := document.Generate("doc/apib/validate.apib"); err != nil { + t.Fatalf("err: %s", err) + } }() mux := http.NewServeMux() diff --git a/httpdoc.go b/httpdoc.go index 0a4741d..27e1297 100644 --- a/httpdoc.go +++ b/httpdoc.go @@ -33,8 +33,8 @@ type Document struct { // This is exported just for templating. Entries []Entry - // tmpl is template file to use. Currently this is only static/tmpl/doc.md.tmpl - tmpl string + // Template is template file to use. Default is TmplMarkdown. + Template builtinTmpl logger *log.Logger } diff --git a/static/bindata.go b/static/bindata.go index d6c5f77..2537442 100644 --- a/static/bindata.go +++ b/static/bindata.go @@ -1,6 +1,7 @@ // Code generated by go-bindata. // sources: -// tmpl/doc.md.tmpl +// tmpl/api-blueprint.tmpl +// tmpl/markdown.tmpl // DO NOT EDIT! package static @@ -68,22 +69,42 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _tmplDocMdTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x53\x4d\x8f\xd3\x30\x10\xbd\xfb\x57\x3c\xc9\x07\xe0\xd0\xfc\x00\x6e\x88\x5d\x04\x07\x50\xb5\xac\xb8\xac\x90\xe2\xad\xa7\x8d\x51\x6a\x07\x7b\xca\x87\x9a\xfc\x77\x64\x4f\x0b\x8e\xaa\x72\x80\x4d\x4f\x33\x2f\x33\xf3\xe6\x8d\x5f\x35\x5e\xad\xdf\xc1\x86\x8d\x52\xf7\x9d\x4b\x70\xe9\x0c\x1c\xf6\xe4\xd9\xb0\x0b\x1e\xdb\x10\x71\x3c\xa2\xf9\x60\xf6\x84\x69\x6a\x70\x2e\xdd\x91\xa7\x68\x98\x2c\x1e\x7f\xa2\xed\x98\x07\x1b\x36\x6d\x83\x9b\xe0\x9f\x31\xc8\x3a\xce\x1f\x3a\xe3\x6d\xa3\x94\xd6\xb8\x37\x8f\x3d\x21\x6c\xb1\x09\x9e\xc9\x73\x52\xea\x78\x44\x34\x7e\x47\x68\x6e\x3d\x47\x47\x09\xab\x69\x52\x2b\x3c\x3c\x64\xc6\x3b\x4a\x43\xf0\x89\x3e\xb2\xe1\x43\x7a\x1d\x6c\xe6\xff\x5c\x96\x79\x4f\xdc\x05\x8b\x69\x2a\xd9\xda\x70\x97\x3f\x3d\xd7\x57\xdb\x56\x55\xd7\x88\x3e\x7c\xa7\x78\x46\x4b\xf7\x88\xc4\xd1\x0d\xa9\x37\xa9\xab\x0a\x5e\xe4\x15\xc9\x67\xa6\x6b\xdb\x6a\x8d\x7f\xda\xb6\xcc\x6b\x6e\x28\x6d\xa2\x1b\xca\xa5\x33\xa6\xb5\xc6\x1d\x7d\x3d\x50\xe2\x52\xe0\xb6\x79\x72\xc9\xd7\x26\x9a\xbd\x70\x96\x90\x98\x62\x52\x6a\x44\x79\x19\x8c\xf8\x64\xfa\x43\x09\xea\xa1\xa3\x1a\xb1\xca\x3f\x8c\x78\x39\x0f\x24\xa9\x64\x5d\x12\x8d\xf5\xd3\x43\x32\xa1\xf9\x9d\xce\x15\xc8\x3c\xb9\xd8\xfc\x74\x95\x92\xb7\x64\x2c\x45\x61\x38\xc5\x4b\xe8\xa8\x69\xfe\x4b\x88\xba\xaa\xe4\x8d\xa3\xde\x0a\xc3\x09\xc1\xb6\x40\x4b\xe8\xa9\xc8\x16\x92\x73\xfb\xc3\xec\x87\x9e\x66\x7a\x48\x30\xa5\xda\xb6\xfd\x62\xbe\x19\x99\xaa\xc4\xf2\xb3\xae\x69\xca\x35\xf5\x74\x71\xb3\xfc\x2f\x2a\x2e\x01\x96\x77\xc1\x25\xcf\xd3\xdf\x4d\x38\x66\x3e\x10\x68\x19\x23\x5c\xd0\x2d\xa5\x68\x6e\x85\x93\xa4\xbf\x7a\x61\xde\x77\x61\x86\x3f\xd1\xaf\x00\x00\x00\xff\xff\x4f\x15\x64\x45\x77\x06\x00\x00") +var _tmplApiBlueprintTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x90\x41\x4b\xc4\x30\x14\x84\xef\xfd\x15\x0f\xbc\x28\xcb\xd6\xbb\x47\x75\xc5\x8b\xb2\x28\x78\x7f\x6c\x46\x37\xd2\x26\x31\x79\x05\xa5\xe4\xbf\x4b\x9a\x2c\xa9\x5b\x05\x05\x73\xca\x24\x93\x6f\xe6\xe5\x84\xc6\x91\xda\x7b\xee\x41\x31\x36\xcd\x38\x92\x67\xf3\x02\x6a\x37\x46\xbc\x46\xa0\x75\x3a\xce\xae\x3b\xc8\xde\x2a\x8a\x71\x52\x5b\x96\x7d\x7a\x93\xf6\xd7\x08\x3b\xaf\x9d\x68\x6b\xca\x91\x7e\xa6\xf6\x01\x6f\x03\x82\xdc\x68\x74\x2a\x83\x2a\x7e\x79\xb7\x9a\x37\xa1\x35\x7d\xcf\x85\x51\x8b\xdd\x2c\x6b\xcb\x9e\xfb\x03\x6f\x12\x10\xf8\xb0\x0c\x2e\xc6\x18\x89\xfe\x12\x5c\x73\x57\x54\x48\x74\xca\xce\x75\x7a\xc7\xc9\x7c\xfe\x1a\xac\x39\x3b\x2a\x75\x0b\x56\xf0\x29\xac\x49\x69\x45\x2e\x3b\x55\x1f\x95\x35\x2b\x76\x31\x89\x27\xee\x06\xfc\xf4\x15\x09\x7e\x69\xd5\x47\x53\x1f\x17\xf2\xe6\x9d\x7b\xd7\xe1\xd0\x3b\x38\x6b\x02\x8a\x21\x8b\x47\x61\x19\xc2\x95\x55\xf8\xf2\xa9\xf9\xf2\x17\x03\x1c\x1b\xff\x71\x82\x8c\x9e\x8d\x50\xfd\x9f\x01\x00\x00\xff\xff\x46\x3d\x76\xe7\xbe\x02\x00\x00") -func tmplDocMdTmplBytes() ([]byte, error) { +func tmplApiBlueprintTmplBytes() ([]byte, error) { return bindataRead( - _tmplDocMdTmpl, - "tmpl/doc.md.tmpl", + _tmplApiBlueprintTmpl, + "tmpl/api-blueprint.tmpl", ) } -func tmplDocMdTmpl() (*asset, error) { - bytes, err := tmplDocMdTmplBytes() +func tmplApiBlueprintTmpl() (*asset, error) { + bytes, err := tmplApiBlueprintTmplBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "tmpl/doc.md.tmpl", size: 1655, mode: os.FileMode(420), modTime: time.Unix(1496778555, 0)} + info := bindataFileInfo{name: "tmpl/api-blueprint.tmpl", size: 702, mode: os.FileMode(420), modTime: time.Unix(1496900861, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _tmplMarkdownTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x53\x4d\x8f\xd3\x30\x10\xbd\xfb\x57\x3c\xc9\x07\xe0\xd0\xfc\x00\x6e\x88\x5d\x04\x07\x50\xb5\xac\xb8\xac\x90\xe2\xad\xa7\x8d\x51\x6a\x07\x7b\xca\x87\x9a\xfc\x77\x64\x4f\x0b\x8e\xaa\x72\x80\x4d\x4f\x33\x2f\x33\xf3\xe6\x8d\x5f\x35\x5e\xad\xdf\xc1\x86\x8d\x52\xf7\x9d\x4b\x70\xe9\x0c\x1c\xf6\xe4\xd9\xb0\x0b\x1e\xdb\x10\x71\x3c\xa2\xf9\x60\xf6\x84\x69\x6a\x70\x2e\xdd\x91\xa7\x68\x98\x2c\x1e\x7f\xa2\xed\x98\x07\x1b\x36\x6d\x83\x9b\xe0\x9f\x31\xc8\x3a\xce\x1f\x3a\xe3\x6d\xa3\x94\xd6\xb8\x37\x8f\x3d\x21\x6c\xb1\x09\x9e\xc9\x73\x52\xea\x78\x44\x34\x7e\x47\x68\x6e\x3d\x47\x47\x09\xab\x69\x52\x2b\x3c\x3c\x64\xc6\x3b\x4a\x43\xf0\x89\x3e\xb2\xe1\x43\x7a\x1d\x6c\xe6\xff\x5c\x96\x79\x4f\xdc\x05\x8b\x69\x2a\xd9\xda\x70\x97\x3f\x3d\xd7\x57\xdb\x56\x55\xd7\x88\x3e\x7c\xa7\x78\x46\x4b\xf7\x88\xc4\xd1\x0d\xa9\x37\xa9\xab\x0a\x5e\xe4\x15\xc9\x67\xa6\x6b\xdb\x6a\x8d\x7f\xda\xb6\xcc\x6b\x6e\x28\x6d\xa2\x1b\xca\xa5\x33\xa6\xb5\xc6\x1d\x7d\x3d\x50\xe2\x52\xe0\xb6\x79\x72\xc9\xd7\x26\x9a\xbd\x70\x96\x90\x98\x62\x52\x6a\x44\x79\x19\x8c\xf8\x64\xfa\x43\x09\xea\xa1\xa3\x1a\xb1\xca\x3f\x8c\x78\x39\x0f\x24\xa9\x64\x5d\x12\x8d\xf5\xd3\x43\x32\xa1\xf9\x9d\xce\x15\xc8\x3c\xb9\xd8\xfc\x74\x95\x92\xb7\x64\x2c\x45\x61\x38\xc5\x4b\xe8\xa8\x69\xfe\x4b\x88\xba\xaa\xe4\x8d\xa3\xde\x0a\xc3\x09\xc1\xb6\x40\x4b\xe8\xa9\xc8\x16\x92\x73\xfb\xc3\xec\x87\x9e\x66\x7a\x48\x30\xa5\xda\xb6\xfd\x62\xbe\x19\x99\xaa\xc4\xf2\xb3\xae\x69\xca\x35\xf5\x74\x71\xb3\xfc\x2f\x2a\x2e\x01\x96\x77\xc1\x25\xcf\xd3\xdf\x4d\x38\x66\x3e\x10\x68\x19\x23\x5c\xd0\x2d\xa5\x68\x6e\x85\x93\xa4\xbf\x7a\x61\xde\x77\x61\x86\x3f\xd1\xaf\x00\x00\x00\xff\xff\x4f\x15\x64\x45\x77\x06\x00\x00") + +func tmplMarkdownTmplBytes() ([]byte, error) { + return bindataRead( + _tmplMarkdownTmpl, + "tmpl/markdown.tmpl", + ) +} + +func tmplMarkdownTmpl() (*asset, error) { + bytes, err := tmplMarkdownTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "tmpl/markdown.tmpl", size: 1655, mode: os.FileMode(420), modTime: time.Unix(1496894669, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -140,7 +161,8 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "tmpl/doc.md.tmpl": tmplDocMdTmpl, + "tmpl/api-blueprint.tmpl": tmplApiBlueprintTmpl, + "tmpl/markdown.tmpl": tmplMarkdownTmpl, } // AssetDir returns the file names below a certain @@ -184,7 +206,8 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ "tmpl": &bintree{nil, map[string]*bintree{ - "doc.md.tmpl": &bintree{tmplDocMdTmpl, map[string]*bintree{}}, + "api-blueprint.tmpl": &bintree{tmplApiBlueprintTmpl, map[string]*bintree{}}, + "markdown.tmpl": &bintree{tmplMarkdownTmpl, map[string]*bintree{}}, }}, }} diff --git a/static/tmpl/api-blueprint.tmpl b/static/tmpl/api-blueprint.tmpl new file mode 100644 index 0000000..0c0c04d --- /dev/null +++ b/static/tmpl/api-blueprint.tmpl @@ -0,0 +1,32 @@ +# {{ .Name }} + +{{ range .Entries -}} + +# {{ .Method }} {{ .Path }} +{{ .Description }} +{{ if .RequestFields -}} +{{ range .RequestFields -}} ++ {{ .Name }} - {{ .Description }} +{{ end }} +{{ end }} +{{ if .RequestParams -}} ++ Parameters +{{ range .RequestParams }} + {{ .Name }} - {{ .Description }} +{{ end }}{{ end }} ++ Request (application/json) +{{ if .RequestHeaders }} + + Headers +{{ range .RequestHeaders }} {{ .Name }}: {{ .Value }} +{{ end }} +{{ end }} + + Body + {{ .RequestExample }} ++ Response {{ .ResponseStatusCode }} +{{ if .ResponseHeaders }} + + Headers +{{ range .ResponseHeaders }} {{ .Name }}: {{ .Value }} +{{ end }} +{{ end }} + + Body + {{ .ResponseExample }} +{{ end }} diff --git a/static/tmpl/doc.md.tmpl b/static/tmpl/markdown.tmpl similarity index 100% rename from static/tmpl/doc.md.tmpl rename to static/tmpl/markdown.tmpl diff --git a/template.go b/template.go index e75d826..6506da6 100644 --- a/template.go +++ b/template.go @@ -10,8 +10,20 @@ import ( "github.com/mercari/go-httpdoc/static" ) +const ( + // TmplMarkdown is markdown tempalte. + TmplMarkdown builtinTmpl = "tmpl/markdown.tmpl" + + // ExperimentalTmplAPIBlueprint is experimental support for API blueprint template. + // This maybe be modified or deleted. + ExperimentalTmplAPIBlueprint builtinTmpl = "tmpl/api-blueprint.tmpl" +) + // defaultTmpl is default template file to use. -var defaultTmpl = "tmpl/doc.md.tmpl" +var defaultTmpl = TmplMarkdown + +// builtinTmpl is builtin template. +type builtinTmpl string // Generate writes documentation into the given file. Generation is skipped // if EnvHTTPDoc is empty. If directory does not exist or any, it returns error. @@ -32,11 +44,11 @@ func (d *Document) Generate(path string) error { } func (d *Document) generate(w io.Writer) error { - if d.tmpl == "" { - d.tmpl = defaultTmpl + if d.Template == "" { + d.Template = defaultTmpl } - buf, err := static.Asset(d.tmpl) + buf, err := static.Asset(string(d.Template)) if err != nil { return err } diff --git a/template_test.go b/template_test.go index 45e1444..ee9d14d 100644 --- a/template_test.go +++ b/template_test.go @@ -92,7 +92,7 @@ func TestTemplateGenerate_InvalidPath(t *testing.T) { func TestTemplateGenerate_InvalidTmpl(t *testing.T) { doc := &Document{ - tmpl: "no-such-template", + Template: "no-such-template", } if err := doc.generate(ioutil.Discard); err == nil { t.Fatalf("expect to be failed")