diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 98922b6fad6..34bde9b42b4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -157,8 +157,8 @@ }, { "ImportPath": "github.com/emicklei/go-restful", - "Comment": "v1.1.3-45-gd487287", - "Rev": "d4872876992d385f0e69b007f154e5633bdb40af" + "Comment": "v1.1.3-54-gbdfb7d4", + "Rev": "bdfb7d41639a84ea7c36df648e5865cd9fbf21e2" }, { "ImportPath": "github.com/evanphx/json-patch", diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-swagger.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-swagger.go new file mode 100644 index 00000000000..7746b5b0705 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/examples/restful-swagger.go @@ -0,0 +1,61 @@ +package main + +import ( + "log" + "net/http" + + "github.com/emicklei/go-restful" + "github.com/emicklei/go-restful/swagger" +) + +type Book struct { + Title string + Author string +} + +func main() { + ws := new(restful.WebService) + ws.Path("/books") + ws.Consumes(restful.MIME_JSON, restful.MIME_XML) + ws.Produces(restful.MIME_JSON, restful.MIME_XML) + restful.Add(ws) + + ws.Route(ws.GET("/{medium}").To(noop). + Doc("Search all books"). + Param(ws.PathParameter("medium", "digital or paperback").DataType("string")). + Param(ws.QueryParameter("language", "en,nl,de").DataType("string")). + Param(ws.HeaderParameter("If-Modified-Since", "last known timestamp").DataType("datetime")). + Do(returns200, returns500)) + + ws.Route(ws.PUT("/{medium}").To(noop). + Doc("Add a new book"). + Param(ws.PathParameter("medium", "digital or paperback").DataType("string")). + Reads(Book{})) + + // You can install the Swagger Service which provides a nice Web UI on your REST API + // You need to download the Swagger HTML5 assets and change the FilePath location in the config below. + // Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field. + config := swagger.Config{ + WebServices: restful.DefaultContainer.RegisteredWebServices(), // you control what services are visible + WebServicesUrl: "http://localhost:8080", + ApiPath: "/apidocs.json", + + // Optionally, specifiy where the UI is located + SwaggerPath: "/apidocs/", + SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"} + swagger.RegisterSwaggerService(config, restful.DefaultContainer) + + log.Printf("start listening on localhost:8080") + server := &http.Server{Addr: ":8080", Handler: restful.DefaultContainer} + log.Fatal(server.ListenAndServe()) +} + +func noop(req *restful.Request, resp *restful.Response) {} + +func returns200(b *restful.RouteBuilder) { + b.Returns(http.StatusOK, "OK", Book{}) +} + +func returns500(b *restful.RouteBuilder) { + b.Returns(http.StatusInternalServerError, "Bummer, something went wrong", nil) +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go index 7f38a0a6478..05a9987600f 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/parameter.go @@ -35,6 +35,7 @@ type ParameterData struct { Required bool AllowableValues map[string]string AllowMultiple bool + DefaultValue string } // Data returns the state of the Parameter @@ -70,26 +71,32 @@ func (p *Parameter) beForm() *Parameter { return p } -// Required sets the required field and return the receiver +// Required sets the required field and returns the receiver func (p *Parameter) Required(required bool) *Parameter { p.data.Required = required return p } -// AllowMultiple sets the allowMultiple field and return the receiver +// AllowMultiple sets the allowMultiple field and returns the receiver func (p *Parameter) AllowMultiple(multiple bool) *Parameter { p.data.AllowMultiple = multiple return p } -// AllowableValues sets the allowableValues field and return the receiver +// AllowableValues sets the allowableValues field and returns the receiver func (p *Parameter) AllowableValues(values map[string]string) *Parameter { p.data.AllowableValues = values return p } -// DataType sets the dataType field and return the receiver +// DataType sets the dataType field and returns the receiver func (p *Parameter) DataType(typeName string) *Parameter { p.data.DataType = typeName return p } + +// DefaultValue sets the default value field and returnw the receiver +func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter { + p.data.DefaultValue = stringRepresentation + return p +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/route_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/route_test.go index 78b3be88fd3..6a104aff8ed 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/route_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/route_test.go @@ -4,6 +4,14 @@ import ( "testing" ) +// accept should match produces +func TestMatchesAcceptPlainTextWhenProducePlainTextAsLast(t *testing.T) { + r := Route{Produces: []string{"application/json", "text/plain"}} + if !r.matchesAccept("text/plain") { + t.Errorf("accept should match text/plain") + } +} + // accept should match produces func TestMatchesAcceptStar(t *testing.T) { r := Route{Produces: []string{"application/xml"}} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md index 6a392cf2500..c9b49044565 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/CHANGES.md @@ -1,5 +1,10 @@ Change history of swagger = +2015-05-25 +- (api break) changed the type of Properties in Model +- (api break) changed the type of Models in ApiDeclaration +- (api break) changed the parameter type of PostBuildDeclarationMapFunc + 2015-04-09 - add ModelBuildable interface for customization of Model diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/README.md b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/README.md index 2efe8f3a054..9376fc10af6 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/README.md +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/README.md @@ -23,6 +23,6 @@ Now, you can install the Swagger WebService for serving the Swagger specificatio Notes -- -- Use RouteBuilder.Operation(..) to set the Nickname field of the API spec +- The Nickname of an Operation is automatically set by finding the name of the function. You can override it using RouteBuilder.Operation(..) - The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints. - Use tag "description" to annotate a struct field with a description to show in the UI \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/api_declaration_list.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/api_declaration_list.go new file mode 100644 index 00000000000..9f4c3690acb --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/api_declaration_list.go @@ -0,0 +1,64 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// ApiDeclarationList maintains an ordered list of ApiDeclaration. +type ApiDeclarationList struct { + List []ApiDeclaration +} + +// At returns the ApiDeclaration by its path unless absent, then ok is false +func (l *ApiDeclarationList) At(path string) (a ApiDeclaration, ok bool) { + for _, each := range l.List { + if each.ResourcePath == path { + return each, true + } + } + return a, false +} + +// Put adds or replaces a ApiDeclaration with this name +func (l *ApiDeclarationList) Put(path string, a ApiDeclaration) { + // maybe replace existing + for i, each := range l.List { + if each.ResourcePath == path { + // replace + l.List[i] = a + return + } + } + // add + l.List = append(l.List, a) +} + +// Do enumerates all the properties, each with its assigned name +func (l *ApiDeclarationList) Do(block func(path string, decl ApiDeclaration)) { + for _, each := range l.List { + block(each.ResourcePath, each) + } +} + +// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty +func (l ApiDeclarationList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.ResourcePath) + buf.WriteString("\": ") + encoder.Encode(each) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} 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 c08586bb5ce..b272b7bface 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 @@ -7,7 +7,7 @@ import ( ) // PostBuildDeclarationMapFunc can be used to modify the api declaration map. -type PostBuildDeclarationMapFunc func(apiDeclarationMap map[string]ApiDeclaration) +type PostBuildDeclarationMapFunc func(apiDeclarationMap *ApiDeclarationList) type Config struct { // url where the services are available, e.g. http://localhost:8080 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 631ad53a334..2ecc49a6c16 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 @@ -13,7 +13,7 @@ type ModelBuildable interface { } type modelBuilder struct { - Models map[string]Model + Models *ModelList } // addModelFrom creates and adds a Model to the builder and detects and calls @@ -23,7 +23,7 @@ func (b modelBuilder) addModelFrom(sample interface{}) { // allow customizations if buildable, ok := sample.(ModelBuildable); ok { modelOrNil = buildable.PostBuildModel(modelOrNil) - b.Models[modelOrNil.Id] = *modelOrNil + b.Models.Put(modelOrNil.Id, *modelOrNil) } } } @@ -38,16 +38,16 @@ func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model { return nil } // see if we already have visited this model - if _, ok := b.Models[modelName]; ok { + if _, ok := b.Models.At(modelName); ok { return nil } sm := Model{ Id: modelName, Required: []string{}, - Properties: map[string]ModelProperty{}} + Properties: ModelPropertyList{}} // reference the model before further initializing (enables recursive structs) - b.Models[modelName] = sm + b.Models.Put(modelName, sm) // check for slice or array if st.Kind() == reflect.Slice || st.Kind() == reflect.Array { @@ -70,11 +70,11 @@ func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model { if b.isPropertyRequired(field) { sm.Required = append(sm.Required, jsonName) } - sm.Properties[jsonName] = prop + sm.Properties.Put(jsonName, prop) } } // update model builder with completed model - b.Models[modelName] = sm + b.Models.Put(modelName, sm) return &sm } @@ -179,13 +179,13 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) { // embedded struct - sub := modelBuilder{map[string]Model{}} + sub := modelBuilder{new(ModelList)} sub.addModel(fieldType, "") subKey := sub.keyFrom(fieldType) // merge properties from sub - subModel := sub.Models[subKey] - for k, v := range subModel.Properties { - model.Properties[k] = v + subModel, _ := sub.Models.At(subKey) + subModel.Properties.Do(func(k string, v ModelProperty) { + model.Properties.Put(k, v) // if subModel says this property is required then include it required := false for _, each := range subModel.Required { @@ -197,15 +197,15 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam if required { model.Required = append(model.Required, k) } - } + }) // add all new referenced models - for key, sub := range sub.Models { + sub.Models.Do(func(key string, sub Model) { if key != subKey { - if _, ok := b.Models[key]; !ok { - b.Models[key] = sub + if _, ok := b.Models.At(key); !ok { + b.Models.Put(key, sub) } } - } + }) // empty name signals skip property return "", prop } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list.go new file mode 100644 index 00000000000..9bb6cb67850 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list.go @@ -0,0 +1,86 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// NamedModel associates a name with a Model (not using its Id) +type NamedModel struct { + Name string + Model Model +} + +// ModelList encapsulates a list of NamedModel (association) +type ModelList struct { + List []NamedModel +} + +// Put adds or replaces a Model by its name +func (l *ModelList) Put(name string, model Model) { + for i, each := range l.List { + if each.Name == name { + // replace + l.List[i] = NamedModel{name, model} + return + } + } + // add + l.List = append(l.List, NamedModel{name, model}) +} + +// At returns a Model by its name, ok is false if absent +func (l *ModelList) At(name string) (m Model, ok bool) { + for _, each := range l.List { + if each.Name == name { + return each.Model, true + } + } + return m, false +} + +// Do enumerates all the models, each with its assigned name +func (l *ModelList) Do(block func(name string, value Model)) { + for _, each := range l.List { + block(each.Name, each.Model) + } +} + +// MarshalJSON writes the ModelList as if it was a map[string]Model +func (l ModelList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.Name) + buf.WriteString("\": ") + encoder.Encode(each.Model) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +// UnmarshalJSON reads back a ModelList. This is an expensive operation. +func (l *ModelList) UnmarshalJSON(data []byte) error { + raw := map[string]interface{}{} + json.NewDecoder(bytes.NewReader(data)).Decode(&raw) + for k, v := range raw { + // produces JSON bytes for each value + data, err := json.Marshal(v) + if err != nil { + return err + } + var m Model + json.NewDecoder(bytes.NewReader(data)).Decode(&m) + l.Put(k, m) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list_test.go new file mode 100644 index 00000000000..9a9ab919b48 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_list_test.go @@ -0,0 +1,48 @@ +package swagger + +import ( + "encoding/json" + "testing" +) + +func TestModelList(t *testing.T) { + m := Model{} + m.Id = "m" + l := ModelList{} + l.Put("m", m) + k, ok := l.At("m") + if !ok { + t.Error("want model back") + } + if got, want := k.Id, "m"; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestModelList_Marshal(t *testing.T) { + l := ModelList{} + m := Model{Id: "myid"} + l.Put("myid", m) + data, err := json.Marshal(l) + if err != nil { + t.Error(err) + } + if got, want := string(data), `{"myid":{"id":"myid","properties":{}}}`; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestModelList_Unmarshal(t *testing.T) { + data := `{"myid":{"id":"myid","properties":{}}}` + l := ModelList{} + if err := json.Unmarshal([]byte(data), &l); err != nil { + t.Error(err) + } + m, ok := l.At("myid") + if !ok { + t.Error("expected myid") + } + if got, want := m.Id, "myid"; got != want { + t.Errorf("got %v want %v", got, want) + } +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list.go new file mode 100644 index 00000000000..3babb194489 --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list.go @@ -0,0 +1,87 @@ +package swagger + +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + +import ( + "bytes" + "encoding/json" +) + +// NamedModelProperty associates a name to a ModelProperty +type NamedModelProperty struct { + Name string + Property ModelProperty +} + +// ModelPropertyList encapsulates a list of NamedModelProperty (association) +type ModelPropertyList struct { + List []NamedModelProperty +} + +// At returns the ModelPropety by its name unless absent, then ok is false +func (l *ModelPropertyList) At(name string) (p ModelProperty, ok bool) { + for _, each := range l.List { + if each.Name == name { + return each.Property, true + } + } + return p, false +} + +// Put adds or replaces a ModelProperty with this name +func (l *ModelPropertyList) Put(name string, prop ModelProperty) { + // maybe replace existing + for i, each := range l.List { + if each.Name == name { + // replace + l.List[i] = NamedModelProperty{Name: name, Property: prop} + return + } + } + // add + l.List = append(l.List, NamedModelProperty{Name: name, Property: prop}) +} + +// Do enumerates all the properties, each with its assigned name +func (l *ModelPropertyList) Do(block func(name string, value ModelProperty)) { + for _, each := range l.List { + block(each.Name, each.Property) + } +} + +// MarshalJSON writes the ModelPropertyList as if it was a map[string]ModelProperty +func (l ModelPropertyList) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + buf.WriteString("{\n") + for i, each := range l.List { + buf.WriteString("\"") + buf.WriteString(each.Name) + buf.WriteString("\": ") + encoder.Encode(each.Property) + if i < len(l.List)-1 { + buf.WriteString(",\n") + } + } + buf.WriteString("}") + return buf.Bytes(), nil +} + +// UnmarshalJSON reads back a ModelPropertyList. This is an expensive operation. +func (l *ModelPropertyList) UnmarshalJSON(data []byte) error { + raw := map[string]interface{}{} + json.NewDecoder(bytes.NewReader(data)).Decode(&raw) + for k, v := range raw { + // produces JSON bytes for each value + data, err := json.Marshal(v) + if err != nil { + return err + } + var m ModelProperty + json.NewDecoder(bytes.NewReader(data)).Decode(&m) + l.Put(k, m) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list_test.go new file mode 100644 index 00000000000..2833ad8fdaa --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/model_property_list_test.go @@ -0,0 +1,47 @@ +package swagger + +import ( + "encoding/json" + "testing" +) + +func TestModelPropertyList(t *testing.T) { + l := ModelPropertyList{} + p := ModelProperty{Description: "d"} + l.Put("p", p) + q, ok := l.At("p") + if !ok { + t.Error("expected p") + } + if got, want := q.Description, "d"; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestModelPropertyList_Marshal(t *testing.T) { + l := ModelPropertyList{} + p := ModelProperty{Description: "d"} + l.Put("p", p) + data, err := json.Marshal(l) + if err != nil { + t.Error(err) + } + if got, want := string(data), `{"p":{"description":"d"}}`; got != want { + t.Errorf("got %v want %v", got, want) + } +} + +func TestModelPropertyList_Unmarshal(t *testing.T) { + data := `{"p":{"description":"d"}}` + l := ModelPropertyList{} + if err := json.Unmarshal([]byte(data), &l); err != nil { + t.Error(err) + } + m, ok := l.At("p") + if !ok { + t.Error("expected p") + } + if got, want := m.Description, "d"; got != want { + t.Errorf("got %v want %v", got, want) + } +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/ordered_route_map.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/ordered_route_map.go index f57163136ad..b33ccfbeb9e 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/ordered_route_map.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/ordered_route_map.go @@ -1,5 +1,9 @@ package swagger +// Copyright 2015 Ernest Micklei. All rights reserved. +// Use of this source code is governed by a license +// that can be found in the LICENSE file. + import "github.com/emicklei/go-restful" type orderedRouteMap struct { diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter.go deleted file mode 100644 index 813007b7d7a..00000000000 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter.go +++ /dev/null @@ -1,29 +0,0 @@ -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 ParameterSorter []Parameter - -func (s ParameterSorter) Len() int { - return len(s) -} -func (s ParameterSorter) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -var typeToSortKey = map[string]string{ - "path": "A", - "query": "B", - "form": "C", - "header": "D", - "body": "E", -} - -func (s ParameterSorter) Less(i, j int) bool { - // use ordering path,query,form,header,body - pi := s[i] - pj := s[j] - return typeToSortKey[pi.ParamType]+pi.Name < typeToSortKey[pj.ParamType]+pj.Name -} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter_test.go deleted file mode 100644 index ef6d9ebd195..00000000000 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/param_sorter_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package swagger - -import ( - "bytes" - "sort" - "testing" -) - -func TestSortParameters(t *testing.T) { - unsorted := []Parameter{ - Parameter{ - Name: "form2", - ParamType: "form", - }, - Parameter{ - Name: "header1", - ParamType: "header", - }, - Parameter{ - Name: "path2", - ParamType: "path", - }, - Parameter{ - Name: "body", - ParamType: "body", - }, - Parameter{ - Name: "path1", - ParamType: "path", - }, - Parameter{ - Name: "form1", - ParamType: "form", - }, - Parameter{ - Name: "query2", - ParamType: "query", - }, - Parameter{ - Name: "query1", - ParamType: "query", - }, - } - sort.Sort(ParameterSorter(unsorted)) - var b bytes.Buffer - for _, p := range unsorted { - b.WriteString(p.Name + ".") - } - if "path1.path2.query1.query2.form1.form2.header1.body." != b.String() { - t.Fatal("sorting has changed:" + b.String()) - } -} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/postbuild_model_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/postbuild_model_test.go index 200b061486d..3e20d2f5b9d 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/postbuild_model_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/postbuild_model_test.go @@ -14,12 +14,12 @@ func (b Boat) PostBuildModel(m *Model) *Model { // add model property (just to test is can be added; is this a real usecase?) extraType := "string" - m.Properties["extra"] = ModelProperty{ + m.Properties.Put("extra", ModelProperty{ Description: "extra description", DataTypeFields: DataTypeFields{ Type: &extraType, }, - } + }) return m } 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 deleted file mode 100644 index e842b4c290d..00000000000 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/swagger/resource_sorter.go +++ /dev/null @@ -1,19 +0,0 @@ -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 4aad3eebf05..288aec67ef6 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 @@ -114,15 +114,15 @@ type TokenEndpoint struct { // 5.2 API Declaration type ApiDeclaration struct { - SwaggerVersion string `json:"swaggerVersion"` - ApiVersion string `json:"apiVersion"` - BasePath string `json:"basePath"` - ResourcePath string `json:"resourcePath"` // must start with / - Apis []Api `json:"apis,omitempty"` - Models map[string]Model `json:"models,omitempty"` - Produces []string `json:"produces,omitempty"` - Consumes []string `json:"consumes,omitempty"` - Authorizations []Authorization `json:"authorizations,omitempty"` + SwaggerVersion string `json:"swaggerVersion"` + ApiVersion string `json:"apiVersion"` + BasePath string `json:"basePath"` + ResourcePath string `json:"resourcePath"` // must start with / + Apis []Api `json:"apis,omitempty"` + Models ModelList `json:"models,omitempty"` + Produces []string `json:"produces,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Authorizations []Authorization `json:"authorizations,omitempty"` } // 5.2.2 API Object @@ -166,12 +166,12 @@ type ResponseMessage struct { // 5.2.6, 5.2.7 Models Object type Model struct { - Id string `json:"id"` - Description string `json:"description,omitempty"` - Required []string `json:"required,omitempty"` - Properties map[string]ModelProperty `json:"properties"` - SubTypes []string `json:"subTypes,omitempty"` - Discriminator string `json:"discriminator,omitempty"` + Id string `json:"id"` + Description string `json:"description,omitempty"` + Required []string `json:"required,omitempty"` + Properties ModelPropertyList `json:"properties"` + SubTypes []string `json:"subTypes,omitempty"` + Discriminator string `json:"discriminator,omitempty"` } // 5.2.8 Properties Object 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 e810f2f35ef..cf38a760e5a 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 @@ -26,7 +26,7 @@ func TestServiceToApi(t *testing.T) { WebServicesUrl: "http://here.com", ApiPath: "/apipath", WebServices: []*restful.WebService{ws}, - PostBuildHandler: func(in map[string]ApiDeclaration) {}, + PostBuildHandler: func(in *ApiDeclarationList) {}, } sws := newSwaggerService(cfg) decl := sws.composeDeclaration(ws, "/tests") @@ -73,7 +73,7 @@ func TestComposeResponseMessages(t *testing.T) { responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: TestItem{}} route := restful.Route{ResponseErrors: responseErrors} decl := new(ApiDeclaration) - decl.Models = map[string]Model{} + decl.Models = ModelList{} msgs := composeResponseMessages(route, decl) if msgs[0].ResponseModel != "swagger.TestItem" { t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel) @@ -86,7 +86,7 @@ func TestComposeResponseMessageArray(t *testing.T) { responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: []TestItem{}} route := restful.Route{ResponseErrors: responseErrors} decl := new(ApiDeclaration) - decl.Models = map[string]Model{} + decl.Models = ModelList{} msgs := composeResponseMessages(route, decl) if msgs[0].ResponseModel != "array[swagger.TestItem]" { t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel) @@ -95,23 +95,23 @@ func TestComposeResponseMessageArray(t *testing.T) { func TestIssue78(t *testing.T) { sws := newSwaggerService(Config{}) - models := map[string]Model{} + models := new(ModelList) sws.addModelFromSampleTo(&Operation{}, true, Response{Items: &[]TestItem{}}, models) - model, ok := models["swagger.Response"] + model, ok := models.At("swagger.Response") if !ok { t.Fatal("missing response model") } if "swagger.Response" != model.Id { t.Fatal("wrong model id:" + model.Id) } - code, ok := model.Properties["Code"] + code, ok := model.Properties.At("Code") if !ok { t.Fatal("missing code") } if "integer" != *code.Type { t.Fatal("wrong code type:" + *code.Type) } - items, ok := model.Properties["Items"] + items, ok := model.Properties.At("Items") if !ok { t.Fatal("missing items") } 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 9ac56c2de4e..885f9dc7316 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 @@ -15,13 +15,13 @@ import ( type SwaggerService struct { config Config - apiDeclarationMap map[string]ApiDeclaration + apiDeclarationMap *ApiDeclarationList } func newSwaggerService(config Config) *SwaggerService { return &SwaggerService{ config: config, - apiDeclarationMap: map[string]ApiDeclaration{}} + apiDeclarationMap: new(ApiDeclarationList)} } // LogInfo is the function that is called when this package needs to log. It defaults to log.Printf @@ -66,13 +66,13 @@ func RegisterSwaggerService(config Config, wsContainer *restful.Container) { // use routes for _, route := range each.Routes() { entry := staticPathFromRoute(route) - _, exists := sws.apiDeclarationMap[entry] + _, exists := sws.apiDeclarationMap.At(entry) if !exists { - sws.apiDeclarationMap[entry] = sws.composeDeclaration(each, entry) + sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry)) } } } else { // use root path - sws.apiDeclarationMap[each.RootPath()] = sws.composeDeclaration(each, each.RootPath()) + sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath())) } } } @@ -139,19 +139,22 @@ 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, ApiVersion: sws.config.ApiVersion} - for k, v := range sws.apiDeclarationMap { + 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) - } - sort.Sort(ResourceSorter(listing.Apis)) + }) resp.WriteAsJson(listing) } func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) { - decl := sws.apiDeclarationMap[composeRootPath(req)] + decl, ok := sws.apiDeclarationMap.At(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 @@ -180,7 +183,7 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix SwaggerVersion: swaggerVersion, BasePath: sws.config.WebServicesUrl, ResourcePath: ws.RootPath(), - Models: map[string]Model{}, + Models: ModelList{}, ApiVersion: ws.Version()} // collect any path parameters @@ -218,8 +221,6 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix for _, param := range route.ParameterDocs { operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data())) } - // sort parameters - sort.Sort(ParameterSorter(operation.Parameters)) sws.addModelsFromRouteTo(&operation, route, &decl) api.Operations = append(api.Operations, operation) @@ -253,7 +254,7 @@ func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (message if isCollection { modelName = "array[" + modelName + "]" } - modelBuilder{decl.Models}.addModel(st, "") + modelBuilder{&decl.Models}.addModel(st, "") // reference the model message.ResponseModel = modelName } @@ -265,10 +266,10 @@ func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (message // 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) + sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models) } if route.WriteSample != nil { - sws.addModelFromSampleTo(operation, true, route.WriteSample, decl.Models) + sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models) } } @@ -289,7 +290,7 @@ func detectCollectionType(st reflect.Type) (bool, reflect.Type) { } // addModelFromSample creates and adds (or overwrites) a Model from a sample resource -func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models map[string]Model) { +func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) { st := reflect.TypeOf(sample) isCollection, st := detectCollectionType(st) modelName := modelBuilder{}.keyFrom(st) @@ -305,8 +306,9 @@ func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse func asSwaggerParameter(param restful.ParameterData) Parameter { return Parameter{ DataTypeFields: DataTypeFields{ - Type: ¶m.DataType, - Format: asFormat(param.DataType), + Type: ¶m.DataType, + Format: asFormat(param.DataType), + DefaultValue: Special(param.DefaultValue), }, Name: param.Name, Description: param.Description, 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 b0544481e1b..6799174db4f 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 @@ -15,8 +15,8 @@ func testJsonFromStruct(t *testing.T, sample interface{}, expectedJson string) b return compareJson(t, string(data), expectedJson) } -func modelsFromStruct(sample interface{}) map[string]Model { - models := map[string]Model{} +func modelsFromStruct(sample interface{}) *ModelList { + models := new(ModelList) builder := modelBuilder{models} builder.addModelFrom(sample) return models @@ -28,12 +28,12 @@ func compareJson(t *testing.T, actualJsonAsString string, expectedJsonAsString s var expectedMap map[string]interface{} json.Unmarshal([]byte(expectedJsonAsString), &expectedMap) if !reflect.DeepEqual(actualMap, expectedMap) { - fmt.Println("---- expected -----") - fmt.Println(withLineNumbers(expectedJsonAsString)) - fmt.Println("---- actual -----") - fmt.Println(withLineNumbers(actualJsonAsString)) - fmt.Println("---- raw -----") - fmt.Println(actualJsonAsString) + t.Log("---- expected -----") + t.Log(withLineNumbers(expectedJsonAsString)) + t.Log("---- actual -----") + t.Log(withLineNumbers(actualJsonAsString)) + t.Log("---- raw -----") + t.Log(actualJsonAsString) t.Error("there are differences") return false } diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/tracer_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/tracer_test.go new file mode 100644 index 00000000000..60c1e9fc09d --- /dev/null +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/tracer_test.go @@ -0,0 +1,18 @@ +package restful + +import "testing" + +// Use like this: +// +// TraceLogger(testLogger{t}) +type testLogger struct { + t *testing.T +} + +func (l testLogger) Print(v ...interface{}) { + l.t.Log(v...) +} + +func (l testLogger) Printf(format string, v ...interface{}) { + l.t.Logf(format, v...) +} diff --git a/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service_test.go b/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service_test.go index 7d060279c95..876740de5d7 100644 --- a/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service_test.go +++ b/Godeps/_workspace/src/github.com/emicklei/go-restful/web_service_test.go @@ -108,6 +108,20 @@ func TestContentType415_POST_Issue170(t *testing.T) { } } +// go test -v -test.run TestContentType406PlainJson ...restful +func TestContentType406PlainJson(t *testing.T) { + tearDown() + TraceLogger(testLogger{t}) + Add(newGetPlainTextOrJsonService()) + httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) + httpRequest.Header.Set("Accept", "text/plain") + httpWriter := httptest.NewRecorder() + DefaultContainer.dispatch(httpWriter, httpRequest) + if got, want := httpWriter.Code, 200; got != want { + t.Errorf("got %v, want %v", got, want) + } +} + // go test -v -test.run TestContentTypeOctet_Issue170 ...restful func TestContentTypeOctet_Issue170(t *testing.T) { tearDown() @@ -155,6 +169,13 @@ func newGetOnlyJsonOnlyService() *WebService { return ws } +func newGetPlainTextOrJsonService() *WebService { + ws := new(WebService).Path("") + ws.Produces("text/plain", "application/json") + ws.Route(ws.GET("/get").To(doNothing)) + return ws +} + func newGetConsumingOctetStreamService() *WebService { ws := new(WebService).Path("") ws.Consumes("application/octet-stream") diff --git a/pkg/api/validation/schema.go b/pkg/api/validation/schema.go index 0bfaa1c9c2a..005991d1896 100644 --- a/pkg/api/validation/schema.go +++ b/pkg/api/validation/schema.go @@ -85,12 +85,12 @@ func (s *SwaggerSchema) ValidateBytes(data []byte) error { func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, typeName string) error { models := s.api.Models // TODO: handle required fields here too. - model, ok := models[typeName] + model, ok := models.At(typeName) if !ok { return fmt.Errorf("couldn't find type: %s", typeName) } properties := model.Properties - if len(properties) == 0 { + if len(properties.List) == 0 { // The object does not have any sub-fields. return nil } @@ -102,7 +102,7 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, t fieldName = fieldName + "." } for key, value := range fields { - details, ok := properties[key] + details, ok := properties.At(key) if !ok { glog.Infof("unknown field: %s", key) // Some properties can be missing because of