diff --git a/parser.go b/parser.go index dd4fee116..b36b3d1cd 100644 --- a/parser.go +++ b/parser.go @@ -407,7 +407,15 @@ func (parser *Parser) isInStructStack(refTypeName string) bool { // ParseDefinitions parses Swagger Api definitions. func (parser *Parser) ParseDefinitions() { - for refTypeName, typeSpec := range parser.registerTypes { + // sort the typeNames so that parsing definitions is deterministic + typeNames := make([]string, 0, len(parser.registerTypes)) + for refTypeName := range parser.registerTypes { + typeNames = append(typeNames, refTypeName) + } + sort.Strings(typeNames) + + for _, refTypeName := range typeNames { + typeSpec := parser.registerTypes[refTypeName] ss := strings.Split(refTypeName, ".") pkgName := ss[0] parser.structStack = nil @@ -432,10 +440,10 @@ func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.Ty parser.structStack = append(parser.structStack, refTypeName) log.Println("Generating " + refTypeName) - parser.swagger.Definitions[refTypeName] = parser.parseTypeExpr(pkgName, typeName, typeSpec.Type, true) + parser.swagger.Definitions[refTypeName] = parser.parseTypeExpr(pkgName, typeName, typeSpec.Type) } -func (parser *Parser) collectRequiredFields(pkgName string, properties map[string]spec.Schema) (requiredFields []string) { +func (parser *Parser) collectRequiredFields(pkgName string, properties map[string]spec.Schema, extraRequired []string) (requiredFields []string) { // created sorted list of properties keys so when we iterate over them it's deterministic ks := make([]string, 0, len(properties)) for k := range properties { @@ -461,6 +469,12 @@ func (parser *Parser) collectRequiredFields(pkgName string, properties map[strin properties[k] = prop } + if extraRequired != nil { + requiredFields = append(requiredFields, extraRequired...) + } + + sort.Strings(requiredFields) + return } @@ -473,7 +487,7 @@ func fullTypeName(pkgName, typeName string) string { // parseTypeExpr parses given type expression that corresponds to the type under // given name and package, and returns swagger schema for it. -func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr, flattenRequired bool) spec.Schema { +func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) spec.Schema { switch expr := typeExpr.(type) { // type Foo struct {...} case *ast.StructType: @@ -482,11 +496,14 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr, return schema } + extraRequired := make([]string, 0) properties := make(map[string]spec.Schema) for _, field := range expr.Fields.List { var fieldProps map[string]spec.Schema + var requiredFromAnon []string if field.Names == nil { - fieldProps = parser.parseAnonymousField(pkgName, field) + fieldProps, requiredFromAnon = parser.parseAnonymousField(pkgName, field) + extraRequired = append(extraRequired, requiredFromAnon...) } else { fieldProps = parser.parseStruct(pkgName, field) } @@ -496,17 +513,16 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr, } } - required := parser.collectRequiredFields(pkgName, properties) + // collect requireds from our properties and anonymous fields + required := parser.collectRequiredFields(pkgName, properties, extraRequired) - // unset required from properties because we've aggregated them - if flattenRequired { - for k, prop := range properties { - tname := prop.SchemaProps.Type[0] - if tname != "object" { - prop.SchemaProps.Required = make([]string, 0) - } - properties[k] = prop + // unset required from properties because we've collected them + for k, prop := range properties { + tname := prop.SchemaProps.Type[0] + if tname != "object" { + prop.SchemaProps.Required = make([]string, 0) } + properties[k] = prop } return spec.Schema{ @@ -528,11 +544,11 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr, // type Foo *Baz case *ast.StarExpr: - return parser.parseTypeExpr(pkgName, typeName, expr.X, true) + return parser.parseTypeExpr(pkgName, typeName, expr.X) // type Foo []Baz case *ast.ArrayType: - itemSchema := parser.parseTypeExpr(pkgName, "", expr.Elt, true) + itemSchema := parser.parseTypeExpr(pkgName, "", expr.Elt) return spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, @@ -723,7 +739,7 @@ func (parser *Parser) parseStruct(pkgName string, field *ast.Field) (properties return } -func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[string]spec.Schema { +func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string) { properties := make(map[string]spec.Schema) fullTypeName := "" @@ -736,7 +752,7 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[ } default: log.Printf("Field type of '%T' is unsupported. Skipping", ftype) - return properties + return properties, []string{} } typeName := fullTypeName @@ -746,7 +762,7 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[ } typeSpec := parser.TypeDefinitions[pkgName][typeName] - schema := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type, false) + schema := parser.parseTypeExpr(pkgName, typeName, typeSpec.Type) schemaType := "unknown" if len(schema.SchemaProps.Type) > 0 { @@ -764,7 +780,7 @@ func (parser *Parser) parseAnonymousField(pkgName string, field *ast.Field) map[ log.Printf("Can't extract properties from a schema of type '%s'", schemaType) } - return properties + return properties, schema.SchemaProps.Required } func (parser *Parser) parseField(field *ast.Field) *structField { diff --git a/parser_test.go b/parser_test.go index 5a18f01d7..c24a27ad6 100644 --- a/parser_test.go +++ b/parser_test.go @@ -212,7 +212,7 @@ func TestGetSchemes(t *testing.T) { } -func TestParseSimpleApi(t *testing.T) { +func TestParseSimpleApi1(t *testing.T) { expected := `{ "swagger": "2.0", "info": { @@ -681,7 +681,33 @@ func TestParseSimpleApi(t *testing.T) { } } }, - "web.Pet5": { + "web.Pet5a": { + "type": "object", + "required": [ + "name", + "odd" + ], + "properties": { + "name": { + "type": "string" + }, + "odd": { + "type": "boolean" + } + } + }, + "web.Pet5b": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "web.Pet5c": { "type": "object", "required": [ "name", diff --git a/testdata/simple/api/api.go b/testdata/simple/api/api.go index 45329470e..ee3bcbd3f 100644 --- a/testdata/simple/api/api.go +++ b/testdata/simple/api/api.go @@ -93,7 +93,17 @@ type Pet3 struct { ID int `json:"id"` } -// @Success 200 {object} web.Pet5 "ok" -func GetPet5() { +// @Success 200 {object} web.Pet5a "ok" +func GetPet5a() { + +} + +// @Success 200 {object} web.Pet5b "ok" +func GetPet5b() { + +} + +// @Success 200 {object} web.Pet5c "ok" +func GetPet5c() { } diff --git a/testdata/simple/web/handler.go b/testdata/simple/web/handler.go index 1546d2f2c..c6adffe7e 100644 --- a/testdata/simple/web/handler.go +++ b/testdata/simple/web/handler.go @@ -79,11 +79,19 @@ type RevValue struct { Crosses []cross.Cross `json:"crosses"` } -type Pet4 struct { +// Below we have Pet5b as base type and Pet5a and Pet5c both have Pet5b as anonymous field, inheriting it's properties +// By using these names we ensure that our test will fill if the order of parsing matters at all + +type Pet5a struct { + *Pet5b + Odd bool `json:"odd" binding:"required"` +} + +type Pet5b struct { Name string `json:"name" binding:"required"` } -type Pet5 struct { - *Pet4 +type Pet5c struct { + *Pet5b Odd bool `json:"odd" binding:"required"` }