Skip to content

Commit

Permalink
adds a command for serving swagger ui
Browse files Browse the repository at this point in the history
prepares for exposing formats through API for extra registrations
  • Loading branch information
casualjim committed Jan 24, 2015
1 parent 20bea99 commit d0f4c50
Show file tree
Hide file tree
Showing 26 changed files with 865 additions and 453 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Currently there is a spec validator tool:
- [x] An object model that serializes to swagger yaml or json
- [x] A tool to work with swagger:
- [x] validate a swagger spec document
- [x] serve swagger UI for any swagger spec file
- [ ] generate stub api based on swagger spec
- [ ] generate client from a swagger spec
- [ ] generate spec document based on the code
Expand All @@ -49,8 +50,7 @@ Currently there is a spec validator tool:
- [x] routing
- [x] validation
- [ ] authorization
- [ ] swagger docs UI
- [ ] swagger editor UI
- [x] swagger docs UI
- [x] Typed JSON Schema implementation
- [x] extended string formats
- uuid, uuid3, uuid4, uuid5
Expand Down
55 changes: 55 additions & 0 deletions cmd/swagger/commands/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package commands

// import (
// "fmt"
// "net"
// "net/http"
// "os"
// "path/filepath"

// "github.com/jessevdk/go-flags"
// "github.com/rs/cors"
// )

// type editor struct {
// }

// // NewEditor creates a new doc command.
// func NewEditor() flags.Commander {
// return &editor{}
// }

// func (d *editor) Execute(args []string) error {
// fileName := "./swagger.json"
// if len(args) > 0 {
// fileName = args[0]
// }

// fi, err := os.Stat(fileName)
// if err != nil {
// return err
// }
// if fi.IsDir() {
// return fmt.Errorf("expected")
// }

// orig := fileName
// if !filepath.IsAbs(fileName) {
// if abs, err := filepath.Abs(fileName); err == nil {
// fileName = abs
// }
// }

// listener, err := net.Listen("tcp", "localhost:0")
// if err != nil {
// return err
// }

// fmt.Printf("serving swagger editor for %s at http://%s/swagger-editor/#/edit\n", orig, listener.Addr())
// opts := cors.Options{
// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"},
// AllowedOrigins: []string{"*"},
// }
// corsMW := cors.New(opts)
// return http.Serve(listener, corsMW.Handler(http.FileServer(http.Dir("."))))
// }
51 changes: 51 additions & 0 deletions cmd/swagger/commands/ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package commands

import (
"fmt"
"net"
"net/http"
"os"
"path/filepath"

"github.com/casualjim/go-swagger/swagger-ui"
"github.com/jessevdk/go-flags"
)

type ui struct {
}

// NewUI creates a new doc command.
func NewUI() flags.Commander {
return &ui{}
}

func (d *ui) Execute(args []string) error {
fileName := "./swagger.json"
if len(args) > 0 {
fileName = args[0]
}

fi, err := os.Stat(fileName)
if err != nil {
return err
}
if fi.IsDir() {
return fmt.Errorf("expected")
}

orig := fileName
if !filepath.IsAbs(fileName) {
if abs, err := filepath.Abs(fileName); err == nil {
fileName = abs
}
}

listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
return err
}

fmt.Printf("serving swagger ui for %s at http://%s/swagger-ui\n", orig, listener.Addr())

return http.Serve(listener, swaggerui.Middleware(fileName, nil))
}
2 changes: 2 additions & 0 deletions cmd/swagger/swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ var opts struct{}
func main() {
parser := flags.NewParser(&opts, flags.Default)
parser.AddCommand("validate", "validate the swagger document", "validate the provided swagger document against a swagger spec", &commands.ValidateSpec{})
// parser.AddCommand("editor", "edit the swagger.json document", "serve the swagger editor with the specified spec file", commands.NewEditor())
parser.AddCommand("ui", "api-docs for the swagger.json document", "serve the swagger ui application with the specified spec file", commands.NewUI())
parser.Parse()
}
2 changes: 2 additions & 0 deletions examples/2.0/petstore/server/api/petstore.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"fmt"
"net/http"
"sync"

Expand Down Expand Up @@ -32,6 +33,7 @@ var getAllPets = swagger.OperationHandlerFunc(func(data interface{}) (interface{
return pets, nil
})
var createPet = swagger.OperationHandlerFunc(func(data interface{}) (interface{}, error) {
fmt.Println(data)
body := data.(map[string]interface{})["pet"]
var pet Pet
reflection.UnmarshalMap(body.(map[string]interface{}), &pet)
Expand Down
18 changes: 11 additions & 7 deletions middleware/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,20 @@ func NewContext(spec *spec.Document, api *swagger.API, routes router.Router) *Co
return &Context{spec: spec, api: api, router: routes}
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *swagger.API) http.Handler {
context := NewContext(spec, api, nil)

func defaultStack(context *Context) http.Handler {
terminator := context.OperationHandlerMiddleware()
validator := context.ValidationMiddleware(terminator)
router := context.RouterMiddleware(validator)
swaggerUI := swaggerui.Middleware(router)
return context.RouterMiddleware(validator)
}

return specMiddleware(context, swaggerUI)
func defaultStackWithUI(context *Context) http.Handler {
return swaggerui.Middleware("", defaultStack(context))
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *swagger.API) http.Handler {
context := NewContext(spec, api, nil)
return specMiddleware(context, defaultStackWithUI(context))
}

type contextKey int8
Expand Down
1 change: 1 addition & 0 deletions middleware/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func newOperationExecutor(ctx *Context) http.Handler {
ctx.Respond(rw, r, route.Produces, err)
return
}

ctx.Respond(rw, r, route.Produces, result)
})
}
2 changes: 1 addition & 1 deletion middleware/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "net/http"

func specMiddleware(ctx *Context, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api-docs" {
if r.URL.Path == "/swagger.json" {
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
rw.Write(ctx.spec.Raw())
Expand Down
530 changes: 288 additions & 242 deletions swagger-ui/bindata.go

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions swagger-ui/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package swaggerui

import (
"io"
"net/http"
"os"

"github.com/casualjim/go-swagger/util"
)

// Middleware serves this static site as middleware on /swagger-ui
// passing nil will make this a regular handler not a middleware
func Middleware(path string, next http.Handler) http.Handler {

assetFS := func() *util.AssetFS {
return &util.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "/"}
}

swaggerUI := util.MiddlewareAt("/swagger-ui", assetFS, next)
if path == "" {
return swaggerUI
}

return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api-docs" {
specFile, err := os.Open(path)
defer specFile.Close()

if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}

rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
io.Copy(rw, specFile)
return
}
swaggerUI.ServeHTTP(rw, r)
})
}
125 changes: 125 additions & 0 deletions update-swagger-editor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/sh

version=2.8.2
vname=v$version

curdir=`pwd`
rm -rf /tmp/swaggereditor || true
cd /tmp
curl -OL'#' https://github.com/swagger-api/swagger-editor/archive/$vname.tar.gz | tar zx
cd swagger-editor-${version}
npm install
bower install
grunt build

[[ -f $curdir/swagger-editor/middleware.go ]] && cp $curdir/swagger-editor/middleware.go ./swagger-editor-middleware.go

rm -rf $curdir/swagger-editor
mv dist $curdir/swagger-editor

cd /tmp
rm -rf swagger-editor-$version

cd $curdir/swagger-editor

go-bindata -pkg=swaggereditor ./...

cat > ./middleware.go <<TEMPLATE
package swaggereditor
import (
"fmt"
"io"
"net/http"
"os"
"github.com/casualjim/go-swagger/util"
)
func serveWritable(path string, rw http.ResponseWriter, r *http.Request) {
if r.Method == "PUT" {
specFile, err := os.Create(path)
defer specFile.Close()
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(specFile, r.Body); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
if err := specFile.Close(); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
}
specFile, err := os.Open(path)
defer specFile.Close()
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
if _, err := io.Copy(rw, specFile); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
}
var defaultConfig = \`{
"codegen": {
"servers": "http://generator.wordnik.com/online/api/gen/servers",
"clients": "http://generator.wordnik.com/online/api/gen/clients",
"server": "http://generator.wordnik.com/online/api/gen/servers/{language}",
"client": "http://generator.wordnik.com/online/api/gen/clients/{language}"
},
"disableCodeGen": true,
"autocompleteExtension": {},
"useBackendForStorage": true,
"backendEndpoint": "/swagger.json",
"backendHelathCheckTimeout": 5000,
"useYamlBackend": false,
"disableFileMenu": true,
"headerBranding": false,
"enableTryIt": true,
"brandingCssClass": "",
"schemaUrl": "/schema/swagger.json"
}\`
// Middleware serves this static site as middleware on /swagger-editor
// passing nil will make this a regular handler not a middleware
func Middleware(path string, next http.Handler) http.Handler {
assetFS := func() *util.AssetFS {
return &util.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "/"}
}
editor := util.MiddlewareAt("/swagger-editor", assetFS, next)
writableAPIDocs := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
fmt.Println("writeable api docs")
if r.URL.Path == "/swagger.json" {
serveWritable(path, rw, r)
return
}
editor.ServeHTTP(rw, r)
})
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/config/defaults.json" {
fmt.Println("serving /config/defaults.json")
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
rw.Write([]byte(defaultConfig))
return
}
writableAPIDocs.ServeHTTP(rw, r)
})
}
TEMPLATE
Loading

0 comments on commit d0f4c50

Please sign in to comment.