Update go-restful to get stable sorting in spec

This reverts commit 4a7071e7f7.
This commit is contained in:
Kris Rousey 2015-05-28 16:43:13 -07:00
parent 46686616d4
commit 22b2c68c02
25 changed files with 535 additions and 177 deletions

4
Godeps/Godeps.json generated
View File

@ -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",

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"}}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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")
}

View File

@ -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: &param.DataType,
Format: asFormat(param.DataType),
Type: &param.DataType,
Format: asFormat(param.DataType),
DefaultValue: Special(param.DefaultValue),
},
Name: param.Name,
Description: param.Description,

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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")

View File

@ -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