diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 79317933298..9e0ec1fb8bf 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -151,8 +151,8 @@ }, { "ImportPath": "github.com/emicklei/go-restful", - "Comment": "v1.1.2-50-g692a500", - "Rev": "692a50017a7049b26cf7ea4ccfc0d8c77369a793" + "Comment": "v1.1.3-9-g49e08a0", + "Rev": "49e08a07d31e2dae8005f28b5360a96746cd6365" }, { "ImportPath": "github.com/evanphx/json-patch", diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md b/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md index 8af137db5ed..9b4be5f62ac 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/README.md @@ -52,6 +52,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo - Automatic CORS request handling (using a filter) - API declaration for Swagger UI (see swagger package) - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) +- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...) ### Resources diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go index 7aae565e94e..e16743202cc 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/container.go @@ -22,6 +22,7 @@ type Container struct { containerFilters []FilterFunction doNotRecover bool // default is false recoverHandleFunc RecoverHandleFunction + serviceErrorHandleFunc ServiceErrorHandleFunction router RouteSelector // default is a RouterJSR311, CurlyRouter is the faster alternative contentEncodingEnabled bool // default is false } @@ -35,6 +36,7 @@ func NewContainer() *Container { containerFilters: []FilterFunction{}, doNotRecover: false, recoverHandleFunc: logStackOnRecover, + serviceErrorHandleFunc: writeServiceError, router: RouterJSR311{}, contentEncodingEnabled: false} } @@ -49,6 +51,16 @@ func (c *Container) RecoverHandler(handler RecoverHandleFunction) { c.recoverHandleFunc = handler } +// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation. +// The first argument is the service error. The second must be used to communicate an error response. +type ServiceErrorHandleFunction func(ServiceError, *Response) + +// ServiceErrorHandler changes the default function (writeServiceError) to be called +// when a ServiceError is detected. +func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) { + c.serviceErrorHandleFunc = handler +} + // DoNotRecover controls whether panics will be caught to return HTTP 500. // If set to true, Route functions are responsible for handling any error situation. // Default value is false = recover from panics. This has performance implications. @@ -125,6 +137,13 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) httpWriter.Write(buffer.Bytes()) } +// writeServiceError is the default ServiceErrorHandleFunction and is called +// when a ServiceError is returned during route selection. Default implementation +// calls resp.WriteErrorString(err.Code, err.Message) +func writeServiceError(err ServiceError, resp *Response) { + resp.WriteErrorString(err.Code, err.Message) +} + // Dispatch the incoming Http Request to a matching WebService. func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) { // Instal panic recovery unless told otherwise @@ -172,7 +191,7 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R switch err.(type) { case ServiceError: ser := err.(ServiceError) - resp.WriteErrorString(ser.Code, ser.Message) + c.serviceErrorHandleFunc(ser, resp) } // TODO }} @@ -217,6 +236,25 @@ func (c Container) Handle(pattern string, handler http.Handler) { c.ServeMux.Handle(pattern, handler) } +// HandleWithFilter registers the handler for the given pattern. +// Container's filter chain is applied for handler. +// If a handler already exists for pattern, HandleWithFilter panics. +func (c Container) HandleWithFilter(pattern string, handler http.Handler) { + f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) { + if len(c.containerFilters) == 0 { + handler.ServeHTTP(httpResponse, httpRequest) + return + } + + chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) { + handler.ServeHTTP(httpResponse, httpRequest) + }} + chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse)) + } + + c.Handle(pattern, http.HandlerFunc(f)) +} + // Filter appends a container FilterFunction. These are called before dispatching // a http.Request to a WebService from the container func (c *Container) Filter(filter FilterFunction) { diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/container_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/container_test.go new file mode 100644 index 00000000000..6e581ae1a1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/container_test.go @@ -0,0 +1,21 @@ +package restful + +import ( + "net/http" + "testing" +) + +// go test -v -test.run TestContainer_computeAllowedMethods ...restful +func TestContainer_computeAllowedMethods(t *testing.T) { + wc := NewContainer() + ws1 := new(WebService).Path("/users") + ws1.Route(ws1.GET("{i}").To(dummy)) + ws1.Route(ws1.POST("{i}").To(dummy)) + wc.Add(ws1) + httpRequest, _ := http.NewRequest("GET", "http://api.his.com/users/1", nil) + rreq := Request{Request: httpRequest} + m := wc.computeAllowedMethods(&rreq) + if len(m) != 2 { + t.Errorf("got %d expected 2 methods, %v", len(m), m) + } +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/cors_filter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/cors_filter.go index ae166b6482b..4769063287b 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/cors_filter.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/cors_filter.go @@ -47,7 +47,7 @@ func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain * } if !included { if trace { - traceLogger.Println("HTTP Origin:%s is not part of %v", origin, c.AllowedDomains) + traceLogger.Printf("HTTP Origin:%s is not part of %v", origin, c.AllowedDomains) } chain.ProcessFilter(req, resp) return @@ -62,6 +62,8 @@ func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain * c.doPreflightRequest(req, resp) } else { c.doActualRequest(req, resp) + chain.ProcessFilter(req, resp) + return } } @@ -70,7 +72,7 @@ func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response // continue processing the response } -func (c CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) { +func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) { if len(c.AllowedMethods) == 0 { c.AllowedMethods = c.Container.computeAllowedMethods(req) } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-routefunction_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-routefunction_test.go index 6d61c5c908c..957c0555078 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-routefunction_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-routefunction_test.go @@ -12,7 +12,7 @@ import ( // It uses the httptest.ResponseRecorder to capture output func getIt(req *restful.Request, resp *restful.Response) { - resp.WriteHeader(404) + resp.WriteHeader(204) } func TestCallFunction(t *testing.T) { @@ -20,10 +20,10 @@ func TestCallFunction(t *testing.T) { req := restful.NewRequest(httpReq) recorder := new(httptest.ResponseRecorder) - resp := restful.NewResponse(recoder) + resp := restful.NewResponse(recorder) getIt(req, resp) - if recorder.Code != 404 { - t.Logf("Missing or wrong status code:%d", recorder.Code) + if recorder.Code != 204 { + t.Fatalf("Missing or wrong status code:%d", recorder.Code) } } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/jsr311_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/jsr311_test.go index b531b7f656a..3e79a6def79 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/jsr311_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/jsr311_test.go @@ -66,6 +66,14 @@ func TestDetectDispatcher(t *testing.T) { // Step 2 tests // +// go test -v -test.run TestISSUE_179 ...restful +func TestISSUE_179(t *testing.T) { + ws1 := new(WebService) + ws1.Route(ws1.GET("/v1/category/{param:*}").To(dummy)) + routes := RouterJSR311{}.selectRoutes(ws1, "/v1/category/sub/sub") + t.Logf("%v", routes) +} + // go test -v -test.run TestISSUE_30 ...restful func TestISSUE_30(t *testing.T) { ws1 := new(WebService).Path("/users") @@ -181,38 +189,6 @@ func containsRoutePath(routes []Route, path string, t *testing.T) bool { return false } -var tempregexs = []struct { - template, regex string - literalCount, varCount int -}{ - {"", "^(/.*)?$", 0, 0}, - {"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", 2, 1}, - {"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", 0, 3}, - {"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", 5, 1}, -} - -func TestTemplateToRegularExpression(t *testing.T) { - ok := true - for i, fixture := range tempregexs { - actual, lCount, vCount, _ := templateToRegularExpression(fixture.template) - if actual != fixture.regex { - t.Logf("regex mismatch, expected:%v , actual:%v, line:%v\n", fixture.regex, actual, i) // 11 = where the data starts - ok = false - } - if lCount != fixture.literalCount { - t.Logf("literal count mismatch, expected:%v , actual:%v, line:%v\n", fixture.literalCount, lCount, i) - ok = false - } - if vCount != fixture.varCount { - t.Logf("variable count mismatch, expected:%v , actual:%v, line:%v\n", fixture.varCount, vCount, i) - ok = false - } - } - if !ok { - t.Fatal("one or more expression did not match") - } -} - // go test -v -test.run TestSortableRouteCandidates ...restful func TestSortableRouteCandidates(t *testing.T) { fixture := &sortableRouteCandidates{} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go index bd5d0c2cb0d..f952985a8d7 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/options_filter.go @@ -9,7 +9,7 @@ import "strings" // OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method // and provides the response with a set of allowed methods for the request URL Path. // As for any filter, you can also install it for a particular WebService within a Container -func (c Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) { +func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) { if "OPTIONS" != req.Request.Method { chain.ProcessFilter(req, resp) return diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression.go index 8749cb5cd41..a921e6f2245 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression.go @@ -6,6 +6,7 @@ package restful import ( "bytes" + "fmt" "regexp" "strings" ) @@ -43,9 +44,21 @@ func templateToRegularExpression(template string) (expression string, literalCou } buffer.WriteString("/") if strings.HasPrefix(each, "{") { - // ignore var spec + // check for regular expression in variable + colon := strings.Index(each, ":") + if colon != -1 { + // extract expression + paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1]) + if paramExpr == "*" { // special case + buffer.WriteString("(.*)") + } else { + buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache + } + } else { + // plain var + buffer.WriteString("([^/]+?)") + } varCount += 1 - buffer.WriteString("([^/]+?)") } else { literalCount += len(each) encoded := each // TODO URI encode diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression_test.go new file mode 100644 index 00000000000..334fcef7398 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/path_expression_test.go @@ -0,0 +1,37 @@ +package restful + +import "testing" + +var tempregexs = []struct { + template, regex string + literalCount, varCount int +}{ + {"", "^(/.*)?$", 0, 0}, + {"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", 2, 1}, + {"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", 0, 3}, + {"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", 5, 1}, + {"/a/{b:*}", "^/a/(.*)(/.*)?$", 1, 1}, + {"/a/{b:[a-z]+}", "^/a/([a-z]+)(/.*)?$", 1, 1}, +} + +func TestTemplateToRegularExpression(t *testing.T) { + ok := true + for i, fixture := range tempregexs { + actual, lCount, vCount, _ := templateToRegularExpression(fixture.template) + if actual != fixture.regex { + t.Logf("regex mismatch, expected:%v , actual:%v, line:%v\n", fixture.regex, actual, i) // 11 = where the data starts + ok = false + } + if lCount != fixture.literalCount { + t.Logf("literal count mismatch, expected:%v , actual:%v, line:%v\n", fixture.literalCount, lCount, i) + ok = false + } + if vCount != fixture.varCount { + t.Logf("variable count mismatch, expected:%v , actual:%v, line:%v\n", fixture.varCount, vCount, i) + ok = false + } + } + if !ok { + t.Fatal("one or more expression did not match") + } +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/route.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/route.go index 59d6e233143..777b8861dc6 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/route.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/route.go @@ -29,6 +29,7 @@ type Route struct { // documentation Doc string + Notes string Operation string ParameterDocs []*Parameter ResponseErrors map[int]ResponseError diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/route_builder.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/route_builder.go index 8f4661944a3..9b06a406f94 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/route_builder.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/route_builder.go @@ -21,6 +21,7 @@ type RouteBuilder struct { filters []FilterFunction // documentation doc string + notes string operation string readSample, writeSample interface{} parameters []*Parameter @@ -79,6 +80,12 @@ func (b *RouteBuilder) Doc(documentation string) *RouteBuilder { return b } +// A verbose explanation of the operation behavior. Optional. +func (b *RouteBuilder) Notes(notes string) *RouteBuilder { + b.notes = notes + return b +} + // Reads tells what resource type will be read from the request payload. Optional. // A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type. func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder { @@ -194,6 +201,7 @@ func (b *RouteBuilder) Build() Route { relativePath: b.currentPath, pathExpr: pathExpr, Doc: b.doc, + Notes: b.notes, Operation: b.operation, ParameterDocs: b.parameters, ResponseErrors: b.errorMap, diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go index 4fca0fad624..90f3cd7e0e8 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/config.go @@ -22,4 +22,6 @@ type Config struct { StaticHandler http.Handler // [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled. DisableCORS bool + // Top-level API version. Is reflected in the resource listing. + ApiVersion string } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go index 68524ecfc53..dfa341cfe0f 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder.go @@ -168,8 +168,11 @@ func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName var pType = "array" prop.Type = &pType elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem()) - prop.Items = []Item{Item{Ref: &elemName}} + prop.Items = &Item{Ref: &elemName} // add|overwrite model for element type + if fieldType.Elem().Kind() == reflect.Ptr { + fieldType = fieldType.Elem() + } b.addModel(fieldType.Elem(), elemName) return jsonName, prop } @@ -182,7 +185,7 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa var pType = "array" prop.Type = &pType elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem()) - prop.Items = []Item{Item{Ref: &elemName}} + prop.Items = &Item{Ref: &elemName} // add|overwrite model for element type b.addModel(fieldType.Elem().Elem(), elemName) } else { @@ -200,6 +203,9 @@ func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonNa } func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string { + if t.Kind() == reflect.Ptr { + return t.String()[1:] + } if t.Name() == "" { return modelName + "." + jsonName } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder_test.go index dd966bdfe65..35b8447763b 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_builder_test.go @@ -95,11 +95,9 @@ func TestS2(t *testing.T) { "properties": { "Ids": { "type": "array", - "items": [ - { - "$ref": "string" - } - ] + "items": { + "$ref": "string" + } } } } @@ -177,11 +175,9 @@ func TestSampleToModelAsJson(t *testing.T) { }, "items": { "type": "array", - "items": [ - { - "$ref": "swagger.item" - } - ] + "items": { + "$ref": "swagger.item" + } }, "root": { "type": "swagger.item", @@ -361,11 +357,9 @@ func TestAnonymousArrayStruct(t *testing.T) { "properties": { "A": { "type": "array", - "items": [ - { - "$ref": "swagger.X.A" - } - ] + "items": { + "$ref": "swagger.X.A" + } } } }, @@ -402,11 +396,9 @@ func TestAnonymousPtrArrayStruct(t *testing.T) { "properties": { "A": { "type": "array", - "items": [ - { - "$ref": "swagger.X.A" - } - ] + "items": { + "$ref": "swagger.X.A" + } } } }, @@ -467,11 +459,9 @@ func TestIssue85(t *testing.T) { "properties": { "Datasets": { "type": "array", - "items": [ - { - "$ref": "swagger.Dataset" - } - ] + "items": { + "$ref": "swagger.Dataset" + } } } }, @@ -483,11 +473,9 @@ func TestIssue85(t *testing.T) { "properties": { "Names": { "type": "array", - "items": [ - { - "$ref": "string" - } - ] + "items": { + "$ref": "string" + } } } } @@ -511,25 +499,17 @@ func TestRecursiveStructure(t *testing.T) { "properties": { "History": { "type": "array", - "items": [ - { - "$ref": "swagger.File" - } - ] + "items": { + "$ref": "swagger.File" + } }, "HistoryPtrs": { "type": "array", - "items": [ - { - "$ref": "swagger.File.HistoryPtrs" - } - ] + "items": { + "$ref": "swagger.File" + } } } - }, - "swagger.File.HistoryPtrs": { - "id": "swagger.File.HistoryPtrs", - "properties": {} } }`) } @@ -669,11 +649,9 @@ func TestRegion_Issue113(t *testing.T) { "properties": { "id": { "type": "array", - "items": [ - { - "$ref": "integer" - } - ] + "items": { + "$ref": "integer" + } }, "name": { "type": "string" @@ -727,3 +705,53 @@ func TestIssue158(t *testing.T) { }` testJsonFromStruct(t, Customer{}, expected) } + +func TestSlices(t *testing.T) { + type Address struct { + Country string `json:"country,omitempty"` + } + expected := `{ + "swagger.Address": { + "id": "swagger.Address", + "properties": { + "country": { + "type": "string" + } + } + }, + "swagger.Customer": { + "id": "swagger.Customer", + "required": [ + "name", + "addresses" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "$ref": "swagger.Address" + } + }, + "name": { + "type": "string" + } + } + } + }` + // both slices (with pointer value and with type value) should have equal swagger representation + { + type Customer struct { + Name string `json:"name"` + Addresses []Address `json:"addresses"` + } + testJsonFromStruct(t, Customer{}, expected) + } + { + type Customer struct { + Name string `json:"name"` + Addresses []*Address `json:"addresses"` + } + testJsonFromStruct(t, Customer{}, expected) + } + +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/resource_sorter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/resource_sorter.go new file mode 100644 index 00000000000..e842b4c290d --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/resource_sorter.go @@ -0,0 +1,19 @@ +package swagger + +// Copyright 2014 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +type ResourceSorter []Resource + +func (s ResourceSorter) Len() int { + return len(s) +} + +func (s ResourceSorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s ResourceSorter) Less(i, j int) bool { + return s[i].Path < s[j].Path +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go index 9f2fe4b2b81..4aad3eebf05 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger.go @@ -13,7 +13,7 @@ type DataTypeFields struct { Enum []string `json:"enum,omitempty"` Minimum string `json:"minimum,omitempty"` Maximum string `json:"maximum,omitempty"` - Items []Item `json:"items,omitempty"` + Items *Item `json:"items,omitempty"` UniqueItems *bool `json:"uniqueItems,omitempty"` } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_test.go index 81e72f61ac3..83f1e6a0efb 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_test.go @@ -103,10 +103,10 @@ func TestIssue78(t *testing.T) { t.Fatal("wrong items type:" + *items.Type) } items_items := items.Items - if len(items_items) == 0 { + if items_items == nil { t.Fatal("missing items->items") } - ref := items_items[0].Ref + ref := items_items.Ref if ref == nil { t.Fatal("missing $ref") } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go index 04da0a12406..78526576db4 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/swagger_webservice.go @@ -2,6 +2,7 @@ package swagger import ( "fmt" + "github.com/emicklei/go-restful" // "github.com/emicklei/hopwatch" "log" @@ -128,7 +129,7 @@ func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.Fil } func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) { - listing := ResourceListing{SwaggerVersion: swaggerVersion} + listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion} for k, v := range sws.apiDeclarationMap { ref := Resource{Path: k} if len(v.Apis) > 0 { // use description of first (could still be empty) @@ -136,6 +137,7 @@ func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Respons } listing.Apis = append(listing.Apis, ref) } + sort.Sort(ResourceSorter(listing.Apis)) resp.WriteAsJson(listing) } @@ -145,7 +147,21 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re if len(sws.config.WebServicesUrl) == 0 { // update base path from the actual request // TODO how to detect https? assume http for now - (&decl).BasePath = fmt.Sprintf("http://%s", req.Request.Host) + 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] + } + (&decl).BasePath = fmt.Sprintf("http://%s", host) } resp.WriteAsJson(decl) } @@ -177,6 +193,7 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix operation := Operation{ Method: route.Method, Summary: route.Doc, + Notes: route.Notes, Type: asDataType(route.WriteSample), Parameters: []Parameter{}, Nickname: route.Operation, diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/utils_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/utils_test.go index 6127bd58e2a..ed5412d4ae9 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/utils_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/utils_test.go @@ -33,15 +33,18 @@ func compareJson(t *testing.T, flatCompare bool, value interface{}, expectedJson return } actual := string(output) - if actual != expectedJsonAsString { - t.Errorf("First mismatch JSON doc at line:%d", indexOfNonMatchingLine(actual, expectedJsonAsString)) - // Use simple fmt to create a pastable output :-) + var actualMap map[string]interface{} + var expectedMap map[string]interface{} + json.Unmarshal(output, &actualMap) + json.Unmarshal([]byte(expectedJsonAsString), &expectedMap) + if !reflect.DeepEqual(actualMap, expectedMap) { fmt.Println("---- expected -----") fmt.Println(withLineNumbers(expectedJsonAsString)) fmt.Println("---- actual -----") fmt.Println(withLineNumbers(actual)) fmt.Println("---- raw -----") fmt.Println(actual) + t.Error("there are differences") } } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go index f3c10e01160..52ba06f5295 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service.go @@ -60,6 +60,12 @@ func (w *WebService) Param(parameter *Parameter) *WebService { // PathParameter creates a new Parameter of kind Path for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) PathParameter(name, description string) *Parameter { + return PathParameter(name, description) +} + +// PathParameter creates a new Parameter of kind Path for documentation purposes. +// It is initialized as required with string as its DataType. +func PathParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true, DataType: "string"}} p.bePath() return p @@ -68,6 +74,12 @@ func (w *WebService) PathParameter(name, description string) *Parameter { // QueryParameter creates a new Parameter of kind Query for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) QueryParameter(name, description string) *Parameter { + return QueryParameter(name, description) +} + +// QueryParameter creates a new Parameter of kind Query for documentation purposes. +// It is initialized as not required with string as its DataType. +func QueryParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beQuery() return p @@ -76,6 +88,12 @@ func (w *WebService) QueryParameter(name, description string) *Parameter { // BodyParameter creates a new Parameter of kind Body for documentation purposes. // It is initialized as required without a DataType. func (w *WebService) BodyParameter(name, description string) *Parameter { + return BodyParameter(name, description) +} + +// BodyParameter creates a new Parameter of kind Body for documentation purposes. +// It is initialized as required without a DataType. +func BodyParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true}} p.beBody() return p @@ -84,6 +102,12 @@ func (w *WebService) BodyParameter(name, description string) *Parameter { // HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) HeaderParameter(name, description string) *Parameter { + return HeaderParameter(name, description) +} + +// HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. +// It is initialized as not required with string as its DataType. +func HeaderParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beHeader() return p @@ -92,6 +116,12 @@ func (w *WebService) HeaderParameter(name, description string) *Parameter { // FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) FormParameter(name, description string) *Parameter { + return FormParameter(name, description) +} + +// FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. +// It is initialized as required with string as its DataType. +func FormParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beForm() return p diff --git a/pkg/api/validation/schema.go b/pkg/api/validation/schema.go index a4cbc0ebdd6..4bfaa7536e7 100644 --- a/pkg/api/validation/schema.go +++ b/pkg/api/validation/schema.go @@ -126,7 +126,7 @@ func (s *SwaggerSchema) validateField(value interface{}, apiVersion, fieldName, if !ok { return NewInvalidTypeError(reflect.Array, reflect.TypeOf(value).Kind(), fieldName) } - arrType := *fieldDetails.Items[0].Ref + arrType := *fieldDetails.Items.Ref for ix := range arr { err := s.validateField(arr[ix], apiVersion, fmt.Sprintf("%s[%d]", fieldName, ix), arrType, nil) if err != nil { diff --git a/pkg/api/validation/v1beta1-swagger.json b/pkg/api/validation/v1beta1-swagger.json index dfcc371bf6d..7eb8663eb4e 100644 --- a/pkg/api/validation/v1beta1-swagger.json +++ b/pkg/api/validation/v1beta1-swagger.json @@ -943,11 +943,9 @@ "properties": { "command": { "type": "array", - "items": [ - { + "items": { "$ref": "string" } - ] }, "cpu": { "type": "integer", @@ -955,11 +953,9 @@ }, "env": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.EnvVar" } - ] }, "image": { "type": "string" @@ -982,11 +978,9 @@ }, "ports": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Port" } - ] }, "privileged": { "type": "boolean" @@ -996,11 +990,9 @@ }, "volumeMounts": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.VolumeMount" } - ] }, "workingDir": { "type": "string" @@ -1018,11 +1010,9 @@ "properties": { "containers": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Container" } - ] }, "id": { "type": "string" @@ -1038,11 +1028,9 @@ }, "volumes": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Volume" } - ] } } }, @@ -1075,11 +1063,9 @@ }, "endpoints": { "type": "array", - "items": [ - { + "items": { "$ref": "string" } - ] }, "id": { "type": "string" @@ -1130,11 +1116,9 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Endpoints" } - ] }, "kind": { "type": "string" @@ -1260,11 +1244,9 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Event" } - ] }, "kind": { "type": "string" @@ -1288,11 +1270,9 @@ "properties": { "command": { "type": "array", - "items": [ - { + "items": { "$ref": "string" } - ] } } }, @@ -1482,22 +1462,18 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Minion" } - ] }, "kind": { "type": "string" }, "minions": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Minion" } - ] }, "namespace": { "type": "string" @@ -1639,11 +1615,9 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Pod" } - ] }, "kind": { "type": "string" @@ -1813,11 +1787,9 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.ReplicationController" } - ] }, "kind": { "type": "string" @@ -1942,11 +1914,9 @@ }, "publicIPs": { "type": "array", - "items": [ - { + "items": { "$ref": "string" } - ] }, "resourceVersion": { "type": "uint64" @@ -1999,11 +1969,9 @@ }, "items": { "type": "array", - "items": [ - { + "items": { "$ref": "v1beta1.Service" } - ] }, "kind": { "type": "string"