summaryrefslogtreecommitdiff
path: root/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go')
-rw-r--r--vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go443
1 files changed, 443 insertions, 0 deletions
diff --git a/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go b/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go
new file mode 100644
index 000000000..d90623120
--- /dev/null
+++ b/vendor/github.com/emicklei/go-restful-swagger12/swagger_webservice.go
@@ -0,0 +1,443 @@
+package swagger
+
+import (
+ "fmt"
+
+ "github.com/emicklei/go-restful"
+ // "github.com/emicklei/hopwatch"
+ "net/http"
+ "reflect"
+ "sort"
+ "strings"
+
+ "github.com/emicklei/go-restful/log"
+)
+
+type SwaggerService struct {
+ config Config
+ apiDeclarationMap *ApiDeclarationList
+}
+
+func newSwaggerService(config Config) *SwaggerService {
+ sws := &SwaggerService{
+ config: config,
+ apiDeclarationMap: new(ApiDeclarationList)}
+
+ // Build all ApiDeclarations
+ for _, each := range config.WebServices {
+ rootPath := each.RootPath()
+ // skip the api service itself
+ if rootPath != config.ApiPath {
+ if rootPath == "" || rootPath == "/" {
+ // use routes
+ for _, route := range each.Routes() {
+ entry := staticPathFromRoute(route)
+ _, exists := sws.apiDeclarationMap.At(entry)
+ if !exists {
+ sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
+ }
+ }
+ } else { // use root path
+ sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
+ }
+ }
+ }
+
+ // if specified then call the PostBuilderHandler
+ if config.PostBuildHandler != nil {
+ config.PostBuildHandler(sws.apiDeclarationMap)
+ }
+ return sws
+}
+
+// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
+var LogInfo = func(format string, v ...interface{}) {
+ // use the restful package-wide logger
+ log.Printf(format, v...)
+}
+
+// InstallSwaggerService add the WebService that provides the API documentation of all services
+// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
+func InstallSwaggerService(aSwaggerConfig Config) {
+ RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
+}
+
+// RegisterSwaggerService add the WebService that provides the API documentation of all services
+// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
+func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
+ sws := newSwaggerService(config)
+ ws := new(restful.WebService)
+ ws.Path(config.ApiPath)
+ ws.Produces(restful.MIME_JSON)
+ if config.DisableCORS {
+ ws.Filter(enableCORS)
+ }
+ ws.Route(ws.GET("/").To(sws.getListing))
+ ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
+ ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
+ LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
+ wsContainer.Add(ws)
+
+ // Check paths for UI serving
+ if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
+ swaggerPathSlash := config.SwaggerPath
+ // path must end with slash /
+ if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
+ LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
+ swaggerPathSlash += "/"
+ }
+
+ LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
+ wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))
+
+ //if we define a custom static handler use it
+ } else if config.StaticHandler != nil && config.SwaggerPath != "" {
+ swaggerPathSlash := config.SwaggerPath
+ // path must end with slash /
+ if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
+ LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
+ swaggerPathSlash += "/"
+
+ }
+ LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
+ wsContainer.Handle(swaggerPathSlash, config.StaticHandler)
+
+ } else {
+ LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
+ }
+}
+
+func staticPathFromRoute(r restful.Route) string {
+ static := r.Path
+ bracket := strings.Index(static, "{")
+ if bracket <= 1 { // result cannot be empty
+ return static
+ }
+ if bracket != -1 {
+ static = r.Path[:bracket]
+ }
+ if strings.HasSuffix(static, "/") {
+ return static[:len(static)-1]
+ } else {
+ return static
+ }
+}
+
+func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
+ if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
+ // prevent duplicate header
+ if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
+ resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
+ }
+ }
+ chain.ProcessFilter(req, resp)
+}
+
+func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
+ listing := sws.produceListing()
+ resp.WriteAsJson(listing)
+}
+
+func (sws SwaggerService) produceListing() ResourceListing {
+ listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion, Info: sws.config.Info}
+ sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
+ ref := Resource{Path: k}
+ if len(v.Apis) > 0 { // use description of first (could still be empty)
+ ref.Description = v.Apis[0].Description
+ }
+ listing.Apis = append(listing.Apis, ref)
+ })
+ return listing
+}
+
+func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
+ decl, ok := sws.produceDeclarations(composeRootPath(req))
+ if !ok {
+ resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
+ return
+ }
+ // unless WebServicesUrl is given
+ if len(sws.config.WebServicesUrl) == 0 {
+ // update base path from the actual request
+ // TODO how to detect https? assume http for now
+ var host string
+ // X-Forwarded-Host or Host or Request.Host
+ hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
+ if !ok || len(hostvalues) == 0 {
+ forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
+ if !ok || len(forwarded) == 0 {
+ // fallback to Host field
+ host = req.Request.Host
+ } else {
+ host = forwarded[0]
+ }
+ } else {
+ host = hostvalues[0]
+ }
+ // inspect Referer for the scheme (http vs https)
+ scheme := "http"
+ if referer := req.Request.Header["Referer"]; len(referer) > 0 {
+ if strings.HasPrefix(referer[0], "https") {
+ scheme = "https"
+ }
+ }
+ decl.BasePath = fmt.Sprintf("%s://%s", scheme, host)
+ }
+ resp.WriteAsJson(decl)
+}
+
+func (sws SwaggerService) produceAllDeclarations() map[string]ApiDeclaration {
+ decls := map[string]ApiDeclaration{}
+ sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
+ decls[k] = v
+ })
+ return decls
+}
+
+func (sws SwaggerService) produceDeclarations(route string) (*ApiDeclaration, bool) {
+ decl, ok := sws.apiDeclarationMap.At(route)
+ if !ok {
+ return nil, false
+ }
+ decl.BasePath = sws.config.WebServicesUrl
+ return &decl, true
+}
+
+// composeDeclaration uses all routes and parameters to create a ApiDeclaration
+func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
+ decl := ApiDeclaration{
+ SwaggerVersion: swaggerVersion,
+ BasePath: sws.config.WebServicesUrl,
+ ResourcePath: pathPrefix,
+ Models: ModelList{},
+ ApiVersion: ws.Version()}
+
+ // collect any path parameters
+ rootParams := []Parameter{}
+ for _, param := range ws.PathParameters() {
+ rootParams = append(rootParams, asSwaggerParameter(param.Data()))
+ }
+ // aggregate by path
+ pathToRoutes := newOrderedRouteMap()
+ for _, other := range ws.Routes() {
+ if strings.HasPrefix(other.Path, pathPrefix) {
+ if len(pathPrefix) > 1 && len(other.Path) > len(pathPrefix) && other.Path[len(pathPrefix)] != '/' {
+ continue
+ }
+ pathToRoutes.Add(other.Path, other)
+ }
+ }
+ pathToRoutes.Do(func(path string, routes []restful.Route) {
+ api := Api{Path: strings.TrimSuffix(withoutWildcard(path), "/"), Description: ws.Documentation()}
+ voidString := "void"
+ for _, route := range routes {
+ operation := Operation{
+ Method: route.Method,
+ Summary: route.Doc,
+ Notes: route.Notes,
+ // Type gets overwritten if there is a write sample
+ DataTypeFields: DataTypeFields{Type: &voidString},
+ Parameters: []Parameter{},
+ Nickname: route.Operation,
+ ResponseMessages: composeResponseMessages(route, &decl, &sws.config)}
+
+ operation.Consumes = route.Consumes
+ operation.Produces = route.Produces
+
+ // share root params if any
+ for _, swparam := range rootParams {
+ operation.Parameters = append(operation.Parameters, swparam)
+ }
+ // route specific params
+ for _, param := range route.ParameterDocs {
+ operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
+ }
+
+ sws.addModelsFromRouteTo(&operation, route, &decl)
+ api.Operations = append(api.Operations, operation)
+ }
+ decl.Apis = append(decl.Apis, api)
+ })
+ return decl
+}
+
+func withoutWildcard(path string) string {
+ if strings.HasSuffix(path, ":*}") {
+ return path[0:len(path)-3] + "}"
+ }
+ return path
+}
+
+// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
+func composeResponseMessages(route restful.Route, decl *ApiDeclaration, config *Config) (messages []ResponseMessage) {
+ if route.ResponseErrors == nil {
+ return messages
+ }
+ // sort by code
+ codes := sort.IntSlice{}
+ for code := range route.ResponseErrors {
+ codes = append(codes, code)
+ }
+ codes.Sort()
+ for _, code := range codes {
+ each := route.ResponseErrors[code]
+ message := ResponseMessage{
+ Code: code,
+ Message: each.Message,
+ }
+ if each.Model != nil {
+ st := reflect.TypeOf(each.Model)
+ isCollection, st := detectCollectionType(st)
+ // collection cannot be in responsemodel
+ if !isCollection {
+ modelName := modelBuilder{}.keyFrom(st)
+ modelBuilder{Models: &decl.Models, Config: config}.addModel(st, "")
+ message.ResponseModel = modelName
+ }
+ }
+ messages = append(messages, message)
+ }
+ return
+}
+
+// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
+func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
+ if route.ReadSample != nil {
+ sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
+ }
+ if route.WriteSample != nil {
+ sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
+ }
+}
+
+func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
+ isCollection := false
+ if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
+ st = st.Elem()
+ isCollection = true
+ } else {
+ if st.Kind() == reflect.Ptr {
+ if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
+ st = st.Elem().Elem()
+ isCollection = true
+ }
+ }
+ }
+ return isCollection, st
+}
+
+// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
+func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
+ mb := modelBuilder{Models: models, Config: &sws.config}
+ if isResponse {
+ sampleType, items := asDataType(sample, &sws.config)
+ operation.Type = sampleType
+ operation.Items = items
+ }
+ mb.addModelFrom(sample)
+}
+
+func asSwaggerParameter(param restful.ParameterData) Parameter {
+ return Parameter{
+ DataTypeFields: DataTypeFields{
+ Type: &param.DataType,
+ Format: asFormat(param.DataType, param.DataFormat),
+ DefaultValue: Special(param.DefaultValue),
+ },
+ Name: param.Name,
+ Description: param.Description,
+ ParamType: asParamType(param.Kind),
+
+ Required: param.Required}
+}
+
+// Between 1..7 path parameters is supported
+func composeRootPath(req *restful.Request) string {
+ path := "/" + req.PathParameter("a")
+ b := req.PathParameter("b")
+ if b == "" {
+ return path
+ }
+ path = path + "/" + b
+ c := req.PathParameter("c")
+ if c == "" {
+ return path
+ }
+ path = path + "/" + c
+ d := req.PathParameter("d")
+ if d == "" {
+ return path
+ }
+ path = path + "/" + d
+ e := req.PathParameter("e")
+ if e == "" {
+ return path
+ }
+ path = path + "/" + e
+ f := req.PathParameter("f")
+ if f == "" {
+ return path
+ }
+ path = path + "/" + f
+ g := req.PathParameter("g")
+ if g == "" {
+ return path
+ }
+ return path + "/" + g
+}
+
+func asFormat(dataType string, dataFormat string) string {
+ if dataFormat != "" {
+ return dataFormat
+ }
+ return "" // TODO
+}
+
+func asParamType(kind int) string {
+ switch {
+ case kind == restful.PathParameterKind:
+ return "path"
+ case kind == restful.QueryParameterKind:
+ return "query"
+ case kind == restful.BodyParameterKind:
+ return "body"
+ case kind == restful.HeaderParameterKind:
+ return "header"
+ case kind == restful.FormParameterKind:
+ return "form"
+ }
+ return ""
+}
+
+func asDataType(any interface{}, config *Config) (*string, *Item) {
+ // If it's not a collection, return the suggested model name
+ st := reflect.TypeOf(any)
+ isCollection, st := detectCollectionType(st)
+ modelName := modelBuilder{}.keyFrom(st)
+ // if it's not a collection we are done
+ if !isCollection {
+ return &modelName, nil
+ }
+
+ // XXX: This is not very elegant
+ // We create an Item object referring to the given model
+ models := ModelList{}
+ mb := modelBuilder{Models: &models, Config: config}
+ mb.addModelFrom(any)
+
+ elemTypeName := mb.getElementTypeName(modelName, "", st)
+ item := new(Item)
+ if mb.isPrimitiveType(elemTypeName) {
+ mapped := mb.jsonSchemaType(elemTypeName)
+ item.Type = &mapped
+ } else {
+ item.Ref = &elemTypeName
+ }
+ tmp := "array"
+ return &tmp, item
+}