Merge pull request #5938 from nikhiljindal/swagger

Updating to the latest go-restful code and regenerating the swagger spec
This commit is contained in:
Clayton Coleman 2015-03-25 16:25:58 -04:00
commit c35593a717
25 changed files with 1557 additions and 503 deletions

4
Godeps/Godeps.json generated
View File

@ -170,8 +170,8 @@
}, },
{ {
"ImportPath": "github.com/emicklei/go-restful", "ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.1.3-10-g62dc65d", "Comment": "v1.1.3-26-g977ac8f",
"Rev": "62dc65d6e51525418cad2bb6f292d3cf7c5e9d0a" "Rev": "977ac8fcbcd2ee33319246f7c91d4b426402dc70"
}, },
{ {
"ImportPath": "github.com/evanphx/json-patch", "ImportPath": "github.com/evanphx/json-patch",

View File

@ -1,5 +1,19 @@
Change history of go-restful Change history of go-restful
= =
2015-03-20
- add configurable logging
2015-03-18
- if not specified, the Operation is derived from the Route function
2015-03-17
- expose Parameter creation functions
- make trace logger an interface
- fix OPTIONSFilter
- customize rendering of ServiceError
- JSR311 router now handles wildcards
- add Notes to Route
2014-11-27 2014-11-27
- (api add) PrettyPrint per response. (as proposed in #167) - (api add) PrettyPrint per response. (as proposed in #167)

View File

@ -53,6 +53,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
- API declaration for Swagger UI (see swagger package) - API declaration for Swagger UI (see swagger package)
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...) - Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
- Configurable (trace) logging
### Resources ### Resources
@ -64,8 +65,8 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
- [gopkg.in](https://gopkg.in/emicklei/go-restful.v1) - [gopkg.in](https://gopkg.in/emicklei/go-restful.v1)
- [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora) - [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora)
[![Build Status](https://drone.io/github.com/emicklei/go-restful/status.png)](https://drone.io/github.com/emicklei/go-restful/latest)[![library users](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/library-users.png)](https://sourcegraph.com/github.com/emicklei/go-restful) [![authors](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/authors.png)](https://sourcegraph.com/github.com/emicklei/go-restful) [![xrefs](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/xrefs.png)](https://sourcegraph.com/github.com/emicklei/go-restful) [![Build Status](https://drone.io/github.com/emicklei/go-restful/status.png)](https://drone.io/github.com/emicklei/go-restful/latest)
(c) 2012 - 2014, http://ernestmicklei.com. MIT License (c) 2012 - 2015, http://ernestmicklei.com. MIT License
Type ```git shortlog -s``` for a full list of contributors. Type ```git shortlog -s``` for a full list of contributors.

View File

@ -7,6 +7,7 @@ package restful
const ( const (
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces() MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces() MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
HEADER_Allow = "Allow" HEADER_Allow = "Allow"
HEADER_Accept = "Accept" HEADER_Accept = "Accept"

View File

@ -7,10 +7,12 @@ package restful
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os"
"runtime" "runtime"
"strings" "strings"
"github.com/emicklei/go-restful/log"
) )
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests. // Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
@ -107,7 +109,8 @@ func (c *Container) Add(service *WebService) *Container {
// cannot have duplicate root paths // cannot have duplicate root paths
for _, each := range c.webServices { for _, each := range c.webServices {
if each.RootPath() == service.RootPath() { if each.RootPath() == service.RootPath() {
log.Fatalf("[restful] WebService with duplicate root path detected:['%v']", each) log.Printf("[restful] WebService with duplicate root path detected:['%v']", each)
os.Exit(1)
} }
} }
// if rootPath was not set then lazy initialize it // if rootPath was not set then lazy initialize it
@ -132,7 +135,7 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter)
} }
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line)) buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
} }
log.Println(buffer.String()) log.Print(buffer.String())
httpWriter.WriteHeader(http.StatusInternalServerError) httpWriter.WriteHeader(http.StatusInternalServerError)
httpWriter.Write(buffer.Bytes()) httpWriter.Write(buffer.Bytes())
} }
@ -171,7 +174,7 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R
var err error var err error
writer, err = NewCompressingResponseWriter(httpWriter, encoding) writer, err = NewCompressingResponseWriter(httpWriter, encoding)
if err != nil { if err != nil {
log.Println("[restful] unable to install compressor:", err) log.Print("[restful] unable to install compressor: ", err)
httpWriter.WriteHeader(http.StatusInternalServerError) httpWriter.WriteHeader(http.StatusInternalServerError)
return return
} }

View File

@ -32,7 +32,7 @@ func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *
origin := req.Request.Header.Get(HEADER_Origin) origin := req.Request.Header.Get(HEADER_Origin)
if len(origin) == 0 { if len(origin) == 0 {
if trace { if trace {
traceLogger.Println("no Http header Origin set") traceLogger.Print("no Http header Origin set")
} }
chain.ProcessFilter(req, resp) chain.ProcessFilter(req, resp)
return return

View File

@ -165,10 +165,17 @@ If you expect to read large amounts of payload data, and you do not use this fea
Trouble shooting Trouble shooting
This package has the means to produce detail logging of the complete Http request matching process and filter invocation. This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
Enabling this feature requires you to set a log.Logger instance such as: Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile)) restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
Logging
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
preferred package is simple.
Resources Resources
[project]: https://github.com/emicklei/go-restful [project]: https://github.com/emicklei/go-restful
@ -179,6 +186,6 @@ Resources
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape [showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
(c) 2012-2014, http://ernestmicklei.com. MIT License (c) 2012-2015, http://ernestmicklei.com. MIT License
*/ */
package restful package restful

View File

@ -55,9 +55,9 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R
return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
} }
inputMediaOk := methodOk inputMediaOk := methodOk
// content-type // content-type
contentType := httpRequest.Header.Get(HEADER_ContentType) contentType := httpRequest.Header.Get(HEADER_ContentType)
if httpRequest.ContentLength > 0 {
inputMediaOk = []Route{} inputMediaOk = []Route{}
for _, each := range methodOk { for _, each := range methodOk {
if each.matchesContentType(contentType) { if each.matchesContentType(contentType) {
@ -70,7 +70,7 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R
} }
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
} }
}
// accept // accept
outputMediaOk := []Route{} outputMediaOk := []Route{}
accept := httpRequest.Header.Get(HEADER_Accept) accept := httpRequest.Header.Get(HEADER_Accept)

View File

@ -0,0 +1,31 @@
package log
import (
stdlog "log"
"os"
)
// Logger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger
type StdLogger interface {
Print(v ...interface{})
Printf(format string, v ...interface{})
}
var Logger StdLogger
func init() {
// default Logger
SetLogger(stdlog.New(os.Stdout, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
}
func SetLogger(customLogger StdLogger) {
Logger = customLogger
}
func Print(v ...interface{}) {
Logger.Print(v...)
}
func Printf(format string, v ...interface{}) {
Logger.Printf(format, v...)
}

View File

@ -1,16 +1,32 @@
package restful package restful
import "log"
// Copyright 2014 Ernest Micklei. All rights reserved. // Copyright 2014 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license // Use of this source code is governed by a license
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
import (
"github.com/emicklei/go-restful/log"
)
var trace bool = false var trace bool = false
var traceLogger *log.Logger var traceLogger log.StdLogger
func init() {
traceLogger = log.Logger // use the package logger by default
}
// TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set. // TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set.
func TraceLogger(logger *log.Logger) { // You may call EnableTracing() directly to enable trace logging to the package-wide logger.
func TraceLogger(logger log.StdLogger) {
traceLogger = logger traceLogger = logger
trace = logger != nil EnableTracing(logger != nil)
}
// expose the setter for the global logger on the top-level package
func SetLogger(customLogger log.StdLogger) {
log.SetLogger(customLogger)
}
// EnableTracing can be used to Trace logging on and off.
func EnableTracing(enabled bool) {
trace = enabled
} }

View File

@ -88,8 +88,24 @@ func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
return false return false
} }
// Return whether the mimeType matches to what this Route can consume. // Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
func (r Route) matchesContentType(mimeTypes string) bool { func (r Route) matchesContentType(mimeTypes string) bool {
if len(r.Consumes) == 0 {
// did not specify what it can consume ; any media type (“*/*”) is assumed
return true
}
if len(mimeTypes) == 0 {
// idempotent methods with (most-likely or garanteed) empty content match missing Content-Type
m := r.Method
if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
return true
}
// proceed with default
mimeTypes = MIME_OCTET
}
parts := strings.Split(mimeTypes, ",") parts := strings.Split(mimeTypes, ",")
for _, each := range parts { for _, each := range parts {
var contentType string var contentType string
@ -100,8 +116,8 @@ func (r Route) matchesContentType(mimeTypes string) bool {
} }
// trim before compare // trim before compare
contentType = strings.Trim(contentType, " ") contentType = strings.Trim(contentType, " ")
for _, other := range r.Consumes { for _, consumeableType := range r.Consumes {
if other == "*/*" || other == contentType { if consumeableType == "*/*" || consumeableType == contentType {
return true return true
} }
} }

View File

@ -5,9 +5,12 @@ package restful
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
import ( import (
"log" "os"
"reflect" "reflect"
"runtime"
"strings" "strings"
"github.com/emicklei/go-restful/log"
) )
// RouteBuilder is a helper to construct Routes. // RouteBuilder is a helper to construct Routes.
@ -126,6 +129,7 @@ func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
} }
// Operation allows you to document what the acutal method/function call is of the Route. // Operation allows you to document what the acutal method/function call is of the Route.
// Unless called, the operation name is derived from the RouteFunction set using To(..).
func (b *RouteBuilder) Operation(name string) *RouteBuilder { func (b *RouteBuilder) Operation(name string) *RouteBuilder {
b.operation = name b.operation = name
return b return b
@ -133,7 +137,7 @@ func (b *RouteBuilder) Operation(name string) *RouteBuilder {
// ReturnsError is deprecated, use Returns instead. // ReturnsError is deprecated, use Returns instead.
func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder { func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
log.Println("ReturnsError is deprecated, use Returns instead.") log.Print("ReturnsError is deprecated, use Returns instead.")
return b.Returns(code, message, model) return b.Returns(code, message, model)
} }
@ -186,10 +190,17 @@ func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
func (b *RouteBuilder) Build() Route { func (b *RouteBuilder) Build() Route {
pathExpr, err := newPathExpression(b.currentPath) pathExpr, err := newPathExpression(b.currentPath)
if err != nil { if err != nil {
log.Fatalf("[restful] Invalid path:%s because:%v", b.currentPath, err) log.Printf("[restful] Invalid path:%s because:%v", b.currentPath, err)
os.Exit(1)
} }
if b.function == nil { if b.function == nil {
log.Fatalf("[restful] No function specified for route:" + b.currentPath) log.Printf("[restful] No function specified for route:" + b.currentPath)
os.Exit(1)
}
operationName := b.operation
if len(operationName) == 0 && b.function != nil {
// extract from definition
operationName = nameOfFunction(b.function)
} }
route := Route{ route := Route{
Method: b.httpMethod, Method: b.httpMethod,
@ -202,7 +213,7 @@ func (b *RouteBuilder) Build() Route {
pathExpr: pathExpr, pathExpr: pathExpr,
Doc: b.doc, Doc: b.doc,
Notes: b.notes, Notes: b.notes,
Operation: b.operation, Operation: operationName,
ParameterDocs: b.parameters, ParameterDocs: b.parameters,
ResponseErrors: b.errorMap, ResponseErrors: b.errorMap,
ReadSample: b.readSample, ReadSample: b.readSample,
@ -214,3 +225,16 @@ func (b *RouteBuilder) Build() Route {
func concatPath(path1, path2 string) string { func concatPath(path1, path2 string) string {
return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/") return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
} }
// nameOfFunction returns the short name of the function f for documentation.
// It uses a runtime feature for debugging ; its value may change for later Go versions.
func nameOfFunction(f interface{}) string {
fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
tokenized := strings.Split(fun.Name(), ".")
last := tokenized[len(tokenized)-1]
last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
last = strings.TrimSuffix(last, ")-fm") // Go 1.5
last = strings.TrimSuffix(last, "·fm") // < Go 1.5
last = strings.TrimSuffix(last, "-fm") // Go 1.5
return last
}

View File

@ -52,4 +52,7 @@ func TestRouteBuilder(t *testing.T) {
if r.Consumes[0] != json { if r.Consumes[0] != json {
t.Error("consumes invalid") t.Error("consumes invalid")
} }
if r.Operation != "dummy" {
t.Error("Operation not set")
}
} }

View File

@ -1,5 +1,9 @@
Change history of swagger Change history of swagger
= =
2015-03-17
- preserve order of Routes per WebService in Swagger listing
- fix use of $ref and type in Swagger models
- add api version to listing
2014-11-14 2014-11-14
- operation parameters are now sorted using ordering path,query,form,header,body - operation parameters are now sorted using ordering path,query,form,header,body

View File

@ -6,6 +6,9 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
) )
// PostBuildDeclarationMapFunc can be used to modify the api declaration map.
type PostBuildDeclarationMapFunc func(apiDeclarationMap map[string]ApiDeclaration)
type Config struct { type Config struct {
// url where the services are available, e.g. http://localhost:8080 // url where the services are available, e.g. http://localhost:8080
// if left empty then the basePath of Swagger is taken from the actual request // if left empty then the basePath of Swagger is taken from the actual request
@ -24,4 +27,6 @@ type Config struct {
DisableCORS bool DisableCORS bool
// Top-level API version. Is reflected in the resource listing. // Top-level API version. Is reflected in the resource listing.
ApiVersion string ApiVersion string
// If set then call this handler after building the complete ApiDeclaration Map
PostBuildHandler PostBuildDeclarationMapFunc
} }

View File

@ -78,45 +78,48 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
return "", prop return "", prop
} }
fieldType := field.Type fieldType := field.Type
fieldKind := fieldType.Kind()
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if len(s) > 1 && s[1] == "string" {
fieldType = reflect.TypeOf("")
}
}
var pType = b.jsonSchemaType(fieldType.String()) // may include pkg path
prop.Type = &pType
if b.isPrimitiveType(fieldType.String()) {
prop.Format = b.jsonSchemaFormat(fieldType.String())
return jsonName, prop
}
// check if type is doing its own marshalling
marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem() marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
if fieldType.Implements(marshalerType) { if fieldType.Implements(marshalerType) {
var pType = "string" var pType = "string"
prop.Type = &pType prop.Type = &pType
prop.Format = b.jsonSchemaFormat(fieldType.String())
return jsonName, prop return jsonName, prop
} }
if fieldKind == reflect.Struct { // check if annotation says it is a string
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if len(s) > 1 && s[1] == "string" {
stringt := "string"
prop.Type = &stringt
return jsonName, prop
}
}
fieldKind := fieldType.Kind()
switch {
case fieldKind == reflect.Struct:
return b.buildStructTypeProperty(field, jsonName, model) return b.buildStructTypeProperty(field, jsonName, model)
} case fieldKind == reflect.Slice || fieldKind == reflect.Array:
if fieldKind == reflect.Slice || fieldKind == reflect.Array {
return b.buildArrayTypeProperty(field, jsonName, modelName) return b.buildArrayTypeProperty(field, jsonName, modelName)
} case fieldKind == reflect.Ptr:
if fieldKind == reflect.Ptr {
return b.buildPointerTypeProperty(field, jsonName, modelName) return b.buildPointerTypeProperty(field, jsonName, modelName)
} }
if b.isPrimitiveType(fieldType.String()) {
mapped := b.jsonSchemaType(fieldType.String())
prop.Type = &mapped
prop.Format = b.jsonSchemaFormat(fieldType.String())
return jsonName, prop
}
modelType := fieldType.String()
prop.Ref = &modelType
if fieldType.Name() == "" { // override type of anonymous structs if fieldType.Name() == "" { // override type of anonymous structs
nestedTypeName := modelName + "." + jsonName nestedTypeName := modelName + "." + jsonName
var pType = nestedTypeName prop.Ref = &nestedTypeName
prop.Type = &pType
b.addModel(fieldType, nestedTypeName) b.addModel(fieldType, nestedTypeName)
} }
return jsonName, prop return jsonName, prop
@ -129,7 +132,7 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam
// anonymous // anonymous
anonType := model.Id + "." + jsonName anonType := model.Id + "." + jsonName
b.addModel(fieldType, anonType) b.addModel(fieldType, anonType)
prop.Type = &anonType prop.Ref = &anonType
return jsonName, prop return jsonName, prop
} }
if field.Name == fieldType.Name() && field.Anonymous { if field.Name == fieldType.Name() && field.Anonymous {
@ -159,7 +162,7 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam
// simple struct // simple struct
b.addModel(fieldType, "") b.addModel(fieldType, "")
var pType = fieldType.String() var pType = fieldType.String()
prop.Type = &pType prop.Ref = &pType
return jsonName, prop return jsonName, prop
} }
@ -167,13 +170,19 @@ func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName
fieldType := field.Type fieldType := field.Type
var pType = "array" var pType = "array"
prop.Type = &pType prop.Type = &pType
elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem()) elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
prop.Items = &Item{Ref: &elemName} prop.Items = new(Item)
if b.isPrimitiveType(elemTypeName) {
mapped := b.jsonSchemaType(elemTypeName)
prop.Items.Type = &mapped
} else {
prop.Items.Ref = &elemTypeName
}
// add|overwrite model for element type // add|overwrite model for element type
if fieldType.Elem().Kind() == reflect.Ptr { if fieldType.Elem().Kind() == reflect.Ptr {
fieldType = fieldType.Elem() fieldType = fieldType.Elem()
} }
b.addModel(fieldType.Elem(), elemName) b.addModel(fieldType.Elem(), elemTypeName)
return jsonName, prop return jsonName, prop
} }
@ -191,11 +200,11 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa
} else { } else {
// non-array, pointer type // non-array, pointer type
var pType = fieldType.String()[1:] // no star, include pkg path var pType = fieldType.String()[1:] // no star, include pkg path
prop.Type = &pType prop.Ref = &pType
elemName := "" elemName := ""
if fieldType.Elem().Name() == "" { if fieldType.Elem().Name() == "" {
elemName = modelName + "." + jsonName elemName = modelName + "." + jsonName
prop.Type = &elemName prop.Ref = &elemName
} }
b.addModel(fieldType.Elem(), elemName) b.addModel(fieldType.Elem(), elemName)
} }

View File

@ -14,6 +14,29 @@ func (y YesNo) MarshalJSON() ([]byte, error) {
return []byte("no"), nil return []byte("no"), nil
} }
// clear && go test -v -test.run TestRef_Issue190 ...swagger
func TestRef_Issue190(t *testing.T) {
type User struct {
items []string
}
testJsonFromStruct(t, User{}, `{
"swagger.User": {
"id": "swagger.User",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}`)
}
// clear && go test -v -test.run TestCustomMarshaller_Issue96 ...swagger // clear && go test -v -test.run TestCustomMarshaller_Issue96 ...swagger
func TestCustomMarshaller_Issue96(t *testing.T) { func TestCustomMarshaller_Issue96(t *testing.T) {
type Vote struct { type Vote struct {
@ -96,7 +119,7 @@ func TestS2(t *testing.T) {
"Ids": { "Ids": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "string" "type": "string"
} }
} }
} }
@ -131,7 +154,7 @@ func TestS3(t *testing.T) {
], ],
"properties": { "properties": {
"Nested": { "Nested": {
"type": "swagger.NestedS3" "$ref": "swagger.NestedS3"
} }
} }
} }
@ -180,7 +203,7 @@ func TestSampleToModelAsJson(t *testing.T) {
} }
}, },
"root": { "root": {
"type": "swagger.item", "$ref": "swagger.item",
"description": "root desc" "description": "root desc"
} }
} }
@ -284,7 +307,7 @@ func TestAnonymousStruct(t *testing.T) {
], ],
"properties": { "properties": {
"A": { "A": {
"type": "swagger.X.A" "$ref": "swagger.X.A"
} }
} }
}, },
@ -320,7 +343,7 @@ func TestAnonymousPtrStruct(t *testing.T) {
], ],
"properties": { "properties": {
"A": { "A": {
"type": "swagger.X.A" "$ref": "swagger.X.A"
} }
} }
}, },
@ -474,7 +497,7 @@ func TestIssue85(t *testing.T) {
"Names": { "Names": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "string" "type": "string"
} }
} }
} }
@ -531,7 +554,7 @@ func TestEmbeddedStructA1(t *testing.T) {
], ],
"properties": { "properties": {
"B": { "B": {
"type": "swagger.A1.B" "$ref": "swagger.A1.B"
} }
} }
}, },
@ -605,7 +628,7 @@ func TestStructA3(t *testing.T) {
], ],
"properties": { "properties": {
"B": { "B": {
"type": "swagger.D" "$ref": "swagger.D"
} }
} }
}, },
@ -695,7 +718,7 @@ func TestIssue158(t *testing.T) {
], ],
"properties": { "properties": {
"address": { "address": {
"type": "swagger.Address" "$ref": "swagger.Address"
}, },
"name": { "name": {
"type": "string" "type": "string"

View File

@ -25,7 +25,9 @@ func TestServiceToApi(t *testing.T) {
cfg := Config{ cfg := Config{
WebServicesUrl: "http://here.com", WebServicesUrl: "http://here.com",
ApiPath: "/apipath", ApiPath: "/apipath",
WebServices: []*restful.WebService{ws}} WebServices: []*restful.WebService{ws},
PostBuildHandler: func(in map[string]ApiDeclaration) {},
}
sws := newSwaggerService(cfg) sws := newSwaggerService(cfg)
decl := sws.composeDeclaration(ws, "/tests") decl := sws.composeDeclaration(ws, "/tests")
// checks // checks

View File

@ -5,11 +5,12 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
// "github.com/emicklei/hopwatch" // "github.com/emicklei/hopwatch"
"log"
"net/http" "net/http"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
"github.com/emicklei/go-restful/log"
) )
type SwaggerService struct { type SwaggerService struct {
@ -24,7 +25,10 @@ func newSwaggerService(config Config) *SwaggerService {
} }
// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf // LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
var LogInfo = 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 // InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki). // conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
@ -73,6 +77,11 @@ func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
} }
} }
// if specified then call the PostBuilderHandler
if config.PostBuildHandler != nil {
config.PostBuildHandler(sws.apiDeclarationMap)
}
// Check paths for UI serving // Check paths for UI serving
if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" { if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
swaggerPathSlash := config.SwaggerPath swaggerPathSlash := config.SwaggerPath

View File

@ -32,6 +32,8 @@ func compareJson(t *testing.T, actualJsonAsString string, expectedJsonAsString s
fmt.Println(withLineNumbers(expectedJsonAsString)) fmt.Println(withLineNumbers(expectedJsonAsString))
fmt.Println("---- actual -----") fmt.Println("---- actual -----")
fmt.Println(withLineNumbers(actualJsonAsString)) fmt.Println(withLineNumbers(actualJsonAsString))
fmt.Println("---- raw -----")
fmt.Println(actualJsonAsString)
t.Error("there are differences") t.Error("there are differences")
return false return false
} }

View File

@ -1,11 +1,15 @@
package restful package restful
import (
"os"
"github.com/emicklei/go-restful/log"
)
// Copyright 2013 Ernest Micklei. All rights reserved. // Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license // Use of this source code is governed by a license
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
import "log"
// WebService holds a collection of Route values that bind a Http Method + URL Path to a function. // WebService holds a collection of Route values that bind a Http Method + URL Path to a function.
type WebService struct { type WebService struct {
rootPath string rootPath string
@ -26,7 +30,8 @@ func (w *WebService) compilePathExpression() {
} }
compiled, err := newPathExpression(w.rootPath) compiled, err := newPathExpression(w.rootPath)
if err != nil { if err != nil {
log.Fatalf("[restful] invalid path:%s because:%v", w.rootPath, err) log.Printf("[restful] invalid path:%s because:%v", w.rootPath, err)
os.Exit(1)
} }
w.pathExpr = compiled w.pathExpr = compiled
} }

View File

@ -85,6 +85,50 @@ func TestSelectedRoutePath_Issue100(t *testing.T) {
} }
} }
func TestContentType415_Issue170(t *testing.T) {
tearDown()
Add(newGetOnlyJsonOnlyService())
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
func TestContentType415_POST_Issue170(t *testing.T) {
tearDown()
Add(newPostOnlyJsonOnlyService())
httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil)
httpRequest.Header.Set("Content-Type", "application/json")
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
// go test -v -test.run TestContentTypeOctet_Issue170 ...restful
func TestContentTypeOctet_Issue170(t *testing.T) {
tearDown()
Add(newGetConsumingOctetStreamService())
// with content-type
httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil)
httpRequest.Header.Set("Content-Type", MIME_OCTET)
httpWriter := httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
// without content-type
httpRequest, _ = http.NewRequest("GET", "http://here.com/get", nil)
httpWriter = httptest.NewRecorder()
DefaultContainer.dispatch(httpWriter, httpRequest)
if 200 != httpWriter.Code {
t.Errorf("Expected 200, got %d", httpWriter.Code)
}
}
func newPanicingService() *WebService { func newPanicingService() *WebService {
ws := new(WebService).Path("") ws := new(WebService).Path("")
ws.Route(ws.GET("/fire").To(doPanic)) ws.Route(ws.GET("/fire").To(doPanic))
@ -97,6 +141,27 @@ func newGetOnlyService() *WebService {
return ws return ws
} }
func newPostOnlyJsonOnlyService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/json")
ws.Route(ws.POST("/post").To(doNothing))
return ws
}
func newGetOnlyJsonOnlyService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/json")
ws.Route(ws.GET("/get").To(doNothing))
return ws
}
func newGetConsumingOctetStreamService() *WebService {
ws := new(WebService).Path("")
ws.Consumes("application/octet-stream")
ws.Route(ws.GET("/get").To(doNothing))
return ws
}
func newSelectedRouteTestingService() *WebService { func newSelectedRouteTestingService() *WebService {
ws := new(WebService).Path("") ws := new(WebService).Path("")
ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker)) ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker))
@ -113,3 +178,6 @@ func doPanic(req *Request, resp *Response) {
println("lightning...") println("lightning...")
panic("fire") panic("fire")
} }
func doNothing(req *Request, resp *Response) {
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff