Update go-restful to get field description enhancement and anonymous struct fix.

pull/6/head
Brian Grant 2014-11-18 04:05:49 +00:00
parent 37d2bab7ed
commit 142e58c580
11 changed files with 148 additions and 13 deletions

4
Godeps/Godeps.json generated
View File

@ -65,8 +65,8 @@
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.1.2-34-gcb26ade",
"Rev": "cb26adeb9644200cb4ec7b32be31e024696e8d00"
"Comment": "v1.1.2-38-gab99062",
"Rev": "ab990627e3546d3c6c8ab45c4d887a3d66b1b6ab"
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",

View File

@ -1,5 +1,11 @@
Change history of go-restful
=
2014-11-12
- (api add) ApiVersion(.) for documentation in Swagger UI
2014-11-10
- (api change) struct fields tagged with "description" show up in Swagger UI
2014-10-31
- (api change) ReturnsError -> Returns
- (api add) RouteBuilder.Do(aBuilder) for DRY use of RouteBuilder

View File

@ -1,5 +1,13 @@
Change history of swagger
=
2014-11-14
- operation parameters are now sorted using ordering path,query,form,header,body
2014-11-12
- respect omitempty tag value for embedded structs
- expose ApiVersion of WebService to Swagger ApiDeclaration
2014-05-29
- (api add) Ability to define custom http.Handler to serve swagger-ui static files

View File

@ -15,7 +15,6 @@ Now, you can install the Swagger WebService for serving the Swagger specificatio
config := swagger.Config{
WebServices: restful.RegisteredWebServices(),
WebServicesUrl: "http://localhost:8080",
ApiPath: "/apidocs.json",
SwaggerPath: "/apidocs/",
SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"}
@ -26,3 +25,4 @@ Notes
--
- Use RouteBuilder.Operation(..) to set the Nickname field of the API spec
- 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

@ -43,6 +43,9 @@ func (b modelBuilder) addModel(st reflect.Type, nameOverride string) {
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
jsonName, prop := b.buildProperty(field, &sm, modelName)
if descTag := field.Tag.Get("description"); descTag != "" {
prop.Description = descTag
}
// add if not ommitted
if len(jsonName) != 0 {
// update Required
@ -80,7 +83,6 @@ func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, mod
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
s := strings.Split(jsonTag, ",")
if len(s) > 1 && s[1] == "string" {
prop.Description = "(" + fieldType.String() + " as string)"
fieldType = reflect.TypeOf("")
}
}
@ -139,7 +141,17 @@ func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonNam
subModel := sub.Models[subKey]
for k, v := range subModel.Properties {
model.Properties[k] = v
model.Required = append(model.Required, k)
// if subModel says this property is required then include it
required := false
for _, each := range subModel.Required {
if k == each {
required = true
break
}
}
if required {
model.Required = append(model.Required, k)
}
}
// empty name signals skip property
return "", prop

View File

@ -143,7 +143,7 @@ func TestS3(t *testing.T) {
type sample struct {
id string `swagger:"required"` // TODO
items []item
rootItem item `json:"root"`
rootItem item `json:"root" description:"root desc"`
}
type item struct {
@ -184,7 +184,8 @@ func TestSampleToModelAsJson(t *testing.T) {
]
},
"root": {
"type": "swagger.item"
"type": "swagger.item",
"description": "root desc"
}
}
}
@ -212,8 +213,7 @@ func TestJsonTags(t *testing.T) {
"type": "string"
},
"C": {
"type": "string",
"description": "(int as string)"
"type": "string"
},
"D": {
"type": "integer",
@ -536,7 +536,8 @@ func TestRecursiveStructure(t *testing.T) {
type A1 struct {
B struct {
Id int
Id int
Comment string `json:"comment,omitempty"`
}
}
@ -563,6 +564,9 @@ func TestEmbeddedStructA1(t *testing.T) {
"Id": {
"type": "integer",
"format": "int32"
},
"comment": {
"type": "string"
}
}
}
@ -573,7 +577,9 @@ type A2 struct {
C
}
type C struct {
Id int `json:"B"`
Id int `json:"B"`
Comment string `json:"comment,omitempty"`
Secure bool `json:"secure"`
}
// go test -v -test.run TestEmbeddedStructA2 ...swagger
@ -582,12 +588,19 @@ func TestEmbeddedStructA2(t *testing.T) {
"swagger.A2": {
"id": "swagger.A2",
"required": [
"B"
"B",
"secure"
],
"properties": {
"B": {
"type": "integer",
"format": "int32"
},
"comment": {
"type": "string"
},
"secure": {
"type": "boolean"
}
}
}

View File

@ -0,0 +1,29 @@
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

@ -0,0 +1,52 @@
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

@ -21,6 +21,7 @@ func TestServiceToApi(t *testing.T) {
ws.Consumes(restful.MIME_JSON)
ws.Produces(restful.MIME_XML)
ws.Route(ws.GET("/all").To(dummy).Writes(sample{}))
ws.ApiVersion("1.2.3")
cfg := Config{
WebServicesUrl: "http://here.com",
ApiPath: "/apipath",

View File

@ -155,7 +155,8 @@ func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix
SwaggerVersion: swaggerVersion,
BasePath: sws.config.WebServicesUrl,
ResourcePath: ws.RootPath(),
Models: map[string]Model{}}
Models: map[string]Model{},
ApiVersion: ws.Version()}
// collect any path parameters
rootParams := []Parameter{}
@ -192,6 +193,9 @@ 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)
}

View File

@ -18,6 +18,7 @@ type WebService struct {
pathParameters []*Parameter
filters []FilterFunction
documentation string
apiVersion string
}
// compiledPathExpression ensures that the path is compiled into a RegEx for those routers that need it.
@ -35,6 +36,15 @@ func (w *WebService) compiledPathExpression() *pathExpression {
return w.pathExpr
}
// ApiVersion sets the API version for documentation purposes.
func (w *WebService) ApiVersion(apiVersion string) *WebService {
w.apiVersion = apiVersion
return w
}
// Version returns the API version for documentation purposes.
func (w WebService) Version() string { return w.apiVersion }
// Path specifies the root URL template path of the WebService.
// All Routes will be relative to this path.
func (w *WebService) Path(root string) *WebService {