From df2f589bfdd94da0df51c5fce7c04e580f5bfefc Mon Sep 17 00:00:00 2001 From: chengjin Date: Fri, 13 Dec 2019 13:27:50 +0800 Subject: [PATCH 1/4] 1. parseTypeExpr return poionter to spec.Schema. 2. fully support map type, including map[string]interface{},map[string]primitive. 3. optimization: struct parsed as definition ref. --- parser.go | 127 +++++++++++----------- parser_test.go | 163 ++++++++++++++++++++++------- testdata/composition/expected.json | 9 +- 3 files changed, 190 insertions(+), 109 deletions(-) diff --git a/parser.go b/parser.go index 9fccb23e7..c83f834e7 100644 --- a/parser.go +++ b/parser.go @@ -602,7 +602,7 @@ func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.Ty if err != nil { return err } - parser.swagger.Definitions[refTypeName] = schema + parser.swagger.Definitions[refTypeName] = *schema return nil } @@ -648,28 +648,42 @@ 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) (spec.Schema, error) { - //TODO: return pointer to spec.Schema +func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (*spec.Schema, error) { switch expr := typeExpr.(type) { // type Foo struct {...} case *ast.StructType: refTypeName := fullTypeName(pkgName, typeName) if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed { - return schema, nil + return &schema, nil } return parser.parseStruct(pkgName, expr.Fields) // type Foo Baz case *ast.Ident: + if IsGolangPrimitiveType(expr.Name) { + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: spec.StringOrArray{TransToValidSchemeType(expr.Name)}, + }, + }, nil + } refTypeName := fullTypeName(pkgName, expr.Name) if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok { parser.ParseDefinition(pkgName, expr.Name, typedef) } } - return parser.swagger.Definitions[refTypeName], nil + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + refTypeName), + }, + }, + }, nil + /*schema := parser.swagger.Definitions[refTypeName] + return &schema, nil*/ // type Foo *Baz case *ast.StarExpr: @@ -679,13 +693,13 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) case *ast.ArrayType: itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt) if err != nil { - return spec.Schema{}, err + return &spec.Schema{}, err } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ - Schema: &itemSchema, + Schema: itemSchema, }, }, }, nil @@ -693,28 +707,25 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) // type Foo pkg.Bar case *ast.SelectorExpr: if xIdent, ok := expr.X.(*ast.Ident); ok { - pkgName = xIdent.Name - typeName = expr.Sel.Name - refTypeName := fullTypeName(pkgName, typeName) - if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { - typedef := parser.TypeDefinitions[pkgName][typeName] - parser.ParseDefinition(pkgName, typeName, typedef) - } - return parser.swagger.Definitions[refTypeName], nil + return parser.parseTypeExpr(xIdent.Name, expr.Sel.Name, expr.Sel) } // type Foo map[string]Bar case *ast.MapType: - itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Value) - if err != nil { - return spec.Schema{}, err + var valueSchema spec.SchemaOrBool + if _, ok := expr.Value.(*ast.InterfaceType); ok { + valueSchema.Allows = true + } else { + schema, err := parser.parseTypeExpr(pkgName, "", expr.Value) + if err != nil { + return &spec.Schema{}, err + } + valueSchema.Schema = schema } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Schema: &itemSchema, - }, + Type: []string{"object"}, + AdditionalProperties: &valueSchema, }, }, nil // ... @@ -722,21 +733,21 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr) } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, }, }, nil } -func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.Schema, error) { +func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (*spec.Schema, error) { extraRequired := make([]string, 0) properties := make(map[string]spec.Schema) for _, field := range fields.List { fieldProps, requiredFromAnon, err := parser.parseStructField(pkgName, field) if err != nil { - return spec.Schema{}, err + return &spec.Schema{}, err } extraRequired = append(extraRequired, requiredFromAnon...) for k, v := range fieldProps { @@ -753,7 +764,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S properties[k] = prop } - return spec.Schema{ + return &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: properties, @@ -812,7 +823,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[k] = v } case "array": - properties[typeName] = schema + properties[typeName] = *schema default: Printf("Can't extract properties from a schema of type '%s'", schemaType) } @@ -957,51 +968,33 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } } } else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map - _, err := parser.parseTypeExpr(pkgName, "", astTypeMap.Value) - if err != nil { - return properties, nil, err - } - - fullTypeName, err := getFieldType(astTypeMap.Value) + mapValueScheme, err := parser.parseTypeExpr(pkgName, "", astTypeMap) if err != nil { return properties, nil, err } - mapValueScheme := &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + fullTypeName), - }, - }, - } required := make([]string, 0) if structField.isRequired { required = append(required, structField.name) } - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Required: required, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - AdditionalProperties: &spec.SchemaOrBool{ - Schema: mapValueScheme, - }, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - VendorExtensible: spec.VendorExtensible{ - Extensions: structField.extensions, - }, - } + mapValueScheme.Type = []string{structField.schemaType} + mapValueScheme.Description = desc + mapValueScheme.Format = structField.formatType + mapValueScheme.Required = required + mapValueScheme.Maximum = structField.maximum + mapValueScheme.Minimum = structField.minimum + mapValueScheme.MaxLength = structField.maxLength + mapValueScheme.MinLength = structField.minLength + mapValueScheme.Enum = structField.enums + mapValueScheme.Default = structField.defaultValue + mapValueScheme.SwaggerSchemaProps = spec.SwaggerSchemaProps{ + Example: structField.exampleValue, + ReadOnly: structField.readOnly, + } + mapValueScheme.VendorExtensible = spec.VendorExtensible{ + Extensions: structField.extensions, + } + properties[structField.name] = *mapValueScheme } else { required := make([]string, 0) if structField.isRequired { @@ -1036,7 +1029,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } if len(schema.SchemaProps.Type) > 0 { - properties[structField.name] = schema + properties[structField.name] = *schema return properties, nil, nil } } diff --git a/parser_test.go b/parser_test.go index 01ec1ba1e..115beb7dd 100644 --- a/parser_test.go +++ b/parser_test.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -253,8 +254,13 @@ func TestGetAllGoFileInfo(t *testing.T) { err := p.getAllGoFileInfo(searchDir) assert.NoError(t, err) - assert.NotEmpty(t, p.files["testdata/pet/main.go"]) - assert.NotEmpty(t, p.files["testdata/pet/web/handler.go"]) + if runtime.GOOS == "windows" { + assert.NotEmpty(t, p.files["testdata\\pet\\main.go"]) + assert.NotEmpty(t, p.files["testdata\\pet\\web\\handler.go"]) + } else { + assert.NotEmpty(t, p.files["testdata/pet/main.go"]) + assert.NotEmpty(t, p.files["testdata/pet/web/handler.go"]) + } assert.Equal(t, 2, len(p.files)) } @@ -520,7 +526,9 @@ func TestParseSimpleApi1(t *testing.T) { "type": "array", "items": { "type": "object", - "additionalProperties": {} + "additionalProperties": { + "type": "string" + } } }, "cross.Cross": { @@ -563,18 +571,7 @@ func TestParseSimpleApi1(t *testing.T) { } }, "web.CrossAlias": { - "type": "object", - "properties": { - "Array": { - "type": "array", - "items": { - "type": "string" - } - }, - "String": { - "type": "string" - } - } + "$ref": "#/definitions/cross.Cross" }, "web.IndirectRecursiveTest": { "type": "object", @@ -746,9 +743,7 @@ func TestParseSimpleApi1(t *testing.T) { "type": "integer" }, "middlename": { - "type": "string", - "x-abc": "def", - "x-nullable": true + "type": "string" } } }, @@ -838,22 +833,7 @@ func TestParseSimpleApi1(t *testing.T) { "web.Tags": { "type": "array", "items": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "pets": { - "type": "array", - "items": { - "$ref": "#/definitions/web.Pet" - } - } - } + "$ref": "#/definitions/web.Tag" } } }, @@ -2237,6 +2217,8 @@ func TestParseComposition(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") + + //windows will fail: \r\n \n assert.Equal(t, string(expected), string(b)) } @@ -2251,6 +2233,7 @@ func TestParseImportAliases(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") + //windows will fail: \r\n \n assert.Equal(t, string(expected), string(b)) } @@ -2270,7 +2253,7 @@ func TestParseNested(t *testing.T) { assert.Equal(t, string(expected), string(b)) } -func TestParser_ParseStuctArrayObject(t *testing.T) { +func TestParser_ParseStructArrayObject(t *testing.T) { src := ` package api @@ -2398,6 +2381,116 @@ type ResponseWrapper struct { } +func TestParser_ParseStructMapMember(t *testing.T) { + src := ` +package api + +type MyMapType map[string]string + +type Child struct { + Name string +} + +type Parent struct { + Test1 map[string]interface{} + Test2 map[string]string + Test3 map[string]*string + Test4 map[string]Child + Test5 map[string]*Child + Test6 MyMapType + Test7 []Child + Test8 []*Child +} + +// @Success 200 {object} Parent +// @Router /api/{id} [get] +func Test(){ +} +` + expected := `{ + "api.Child": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "api.MyMapType": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "api.Parent": { + "type": "object", + "properties": { + "test1": { + "type": "object", + "additionalProperties": true + }, + "test2": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "test3": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "test4": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/api.Child" + } + }, + "test5": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/api.Child" + } + }, + "test6": { + "type": "object", + "$ref": "#/definitions/api.MyMapType" + }, + "test7": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Child" + } + }, + "test8": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Child" + } + } + } + } +}` + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + typeSpec := p.TypeDefinitions["api"]["Parent"] + err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec) + assert.NoError(t, err) + + out, err := json.MarshalIndent(p.swagger.Definitions, "", " ") + + assert.NoError(t, err) + assert.Equal(t, expected, string(out)) + +} + func TestParser_ParseRouterApiInfoErr(t *testing.T) { src := ` package test diff --git a/testdata/composition/expected.json b/testdata/composition/expected.json index cbf2c1363..d14bfbd91 100644 --- a/testdata/composition/expected.json +++ b/testdata/composition/expected.json @@ -144,12 +144,7 @@ "api.BarMap": { "type": "object", "additionalProperties": { - "type": "object", - "properties": { - "field2": { - "type": "string" - } - } + "$ref": "#/definitions/api.Bar" } }, "api.Foo": { @@ -177,7 +172,7 @@ "field3": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/MapValue" + "$ref": "#/definitions/api.MapValue" } } } From 81c921204a42625e97bfca2a8d7389bcdca4de98 Mon Sep 17 00:00:00 2001 From: chengjin Date: Fri, 13 Dec 2019 15:27:59 +0800 Subject: [PATCH 2/4] remove useless comment --- parser.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/parser.go b/parser.go index c83f834e7..c8cd8a5c1 100644 --- a/parser.go +++ b/parser.go @@ -682,8 +682,6 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) }, }, }, nil - /*schema := parser.swagger.Definitions[refTypeName] - return &schema, nil*/ // type Foo *Baz case *ast.StarExpr: From 0f93d2ad104fe9f6ddd18b0e16e1aa0cb2a03347 Mon Sep 17 00:00:00 2001 From: chengjin Date: Sat, 14 Dec 2019 17:50:34 +0800 Subject: [PATCH 3/4] 1.fix bug: lost pointer member'description . 2.fix bug: lost schema's general information such as field extensions. --- README.md | 2 +- parser.go | 180 ++++++++++++++++++++++++------------------------- parser_test.go | 92 +++++++++++++++++++++---- 3 files changed, 169 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 9fa8dd608..1c912babb 100644 --- a/README.md +++ b/README.md @@ -598,7 +598,7 @@ generated swagger doc as follows: ```go type Account struct { - ID int `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" + ID string `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" } ``` diff --git a/parser.go b/parser.go index c8cd8a5c1..535cca2bf 100644 --- a/parser.go +++ b/parser.go @@ -772,6 +772,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (*spec. type structField struct { name string + desc string schemaType string arrayType string formatType string @@ -788,6 +789,34 @@ type structField struct { extensions map[string]interface{} } +func (sf *structField) toStandardSchema() *spec.Schema { + required := make([]string, 0) + if sf.isRequired { + required = append(required, sf.name) + } + return &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{sf.schemaType}, + Description: sf.desc, + Format: sf.formatType, + Required: required, + Maximum: sf.maximum, + Minimum: sf.minimum, + MaxLength: sf.maxLength, + MinLength: sf.minLength, + Enum: sf.enums, + Default: sf.defaultValue, + }, + SwaggerSchemaProps: spec.SwaggerSchemaProps{ + Example: sf.exampleValue, + ReadOnly: sf.readOnly, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: sf.extensions, + }, + } +} + func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) { properties := map[string]spec.Schema{} @@ -838,17 +867,33 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st if structField.name == "" { return properties, nil, nil } - var desc string - if field.Doc != nil { - desc = strings.TrimSpace(field.Doc.Text()) - } - if desc == "" && field.Comment != nil { - desc = strings.TrimSpace(field.Comment.Text()) - } + // TODO: find package of schemaType and/or arrayType if structField.crossPkg != "" { pkgName = structField.crossPkg } + + fillObject := func(src, dest interface{}) error { + bin, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(bin, dest) + } + + //for spec.Schema have implemented json.Marshaler, here in another way to convert + fillSchema := func(src, dest *spec.Schema) error { + err = fillObject(&src.SchemaProps, &dest.SchemaProps) + if err != nil { + return err + } + err = fillObject(&src.SwaggerSchemaProps, &dest.SwaggerSchemaProps) + if err != nil { + return err + } + return fillObject(&src.VendorExtensible, &dest.VendorExtensible) + } + if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field // write definition if not yet present parser.ParseDefinition(pkgName, structField.schemaType, @@ -860,7 +905,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, // to avoid swagger validation error - Description: desc, + Description: structField.desc, Required: required, Ref: spec.Ref{ Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), @@ -882,7 +927,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Required: required, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -916,7 +961,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -942,7 +987,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st properties[structField.name] = spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{structField.schemaType}, - Description: desc, + Description: structField.desc, Format: structField.formatType, Required: required, Items: &spec.SchemaOrArray{ @@ -966,74 +1011,35 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st } } } else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map - mapValueScheme, err := parser.parseTypeExpr(pkgName, "", astTypeMap) + stdSchema := structField.toStandardSchema() + mapValueSchema, err := parser.parseTypeExpr(pkgName, "", astTypeMap) if err != nil { return properties, nil, err } - - required := make([]string, 0) - if structField.isRequired { - required = append(required, structField.name) - } - mapValueScheme.Type = []string{structField.schemaType} - mapValueScheme.Description = desc - mapValueScheme.Format = structField.formatType - mapValueScheme.Required = required - mapValueScheme.Maximum = structField.maximum - mapValueScheme.Minimum = structField.minimum - mapValueScheme.MaxLength = structField.maxLength - mapValueScheme.MinLength = structField.minLength - mapValueScheme.Enum = structField.enums - mapValueScheme.Default = structField.defaultValue - mapValueScheme.SwaggerSchemaProps = spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - } - mapValueScheme.VendorExtensible = spec.VendorExtensible{ - Extensions: structField.extensions, - } - properties[structField.name] = *mapValueScheme + stdSchema.Type = mapValueSchema.Type + stdSchema.AdditionalProperties = mapValueSchema.AdditionalProperties + properties[structField.name] = *stdSchema } else { - required := make([]string, 0) - if structField.isRequired { - required = append(required, structField.name) - } - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Required: required, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - VendorExtensible: spec.VendorExtensible{ - Extensions: structField.extensions, - }, - } + stdSchema := structField.toStandardSchema() + properties[structField.name] = *stdSchema - if nestStruct, ok := field.Type.(*ast.StarExpr); ok { - schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStruct.X) - if err != nil { - return nil, nil, err - } + if nestStar, ok := field.Type.(*ast.StarExpr); ok { + if !IsGolangPrimitiveType(structField.schemaType) { + schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStar.X) + if err != nil { + return properties, nil, err + } - if len(schema.SchemaProps.Type) > 0 { - properties[structField.name] = *schema - return properties, nil, nil + if len(schema.SchemaProps.Type) > 0 { + err = fillSchema(schema, stdSchema) + if err != nil { + return properties, nil, err + } + properties[structField.name] = *stdSchema + return properties, nil, nil + } } - } - - nestStruct, ok := field.Type.(*ast.StructType) - if ok { + } else if nestStruct, ok := field.Type.(*ast.StructType); ok { props := map[string]spec.Schema{} nestRequired := make([]string, 0) for _, v := range nestStruct.Fields.List { @@ -1049,26 +1055,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st props[k] = v } } - - properties[structField.name] = spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{structField.schemaType}, - Description: desc, - Format: structField.formatType, - Properties: props, - Required: nestRequired, - Maximum: structField.maximum, - Minimum: structField.minimum, - MaxLength: structField.maxLength, - MinLength: structField.minLength, - Enum: structField.enums, - Default: structField.defaultValue, - }, - SwaggerSchemaProps: spec.SwaggerSchemaProps{ - Example: structField.exampleValue, - ReadOnly: structField.readOnly, - }, - } + stdSchema.Properties = props + stdSchema.Required = nestRequired + properties[structField.name] = *stdSchema } } return properties, nil, nil @@ -1132,6 +1121,13 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) { structField.name = toLowerCamelCase(structField.name) } + if field.Doc != nil { + structField.desc = strings.TrimSpace(field.Doc.Text()) + } + if structField.desc == "" && field.Comment != nil { + structField.desc = strings.TrimSpace(field.Comment.Text()) + } + if field.Tag == nil { return structField, nil } diff --git a/parser_test.go b/parser_test.go index 115beb7dd..89b462178 100644 --- a/parser_test.go +++ b/parser_test.go @@ -743,7 +743,9 @@ func TestParseSimpleApi1(t *testing.T) { "type": "integer" }, "middlename": { - "type": "string" + "type": "string", + "x-abc": "def", + "x-nullable": true } } }, @@ -2249,7 +2251,6 @@ func TestParseNested(t *testing.T) { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") - Printf(string(b)) assert.Equal(t, string(expected), string(b)) } @@ -2381,6 +2382,67 @@ type ResponseWrapper struct { } +func TestParser_ParseStructPointerMembers(t *testing.T) { + src := ` +package api + +type Child struct { + Name string +} + +type Parent struct { + Test1 *string //test1 + Test2 *Child //test2 +} + +// @Success 200 {object} Parent +// @Router /api/{id} [get] +func Test(){ +} +` + + expected := `{ + "api.Child": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "api.Parent": { + "type": "object", + "properties": { + "test1": { + "description": "test1", + "type": "string" + }, + "test2": { + "description": "test2", + "type": "object", + "$ref": "#/definitions/api.Child" + } + } + } +}` + + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + p := New() + p.ParseType(f) + err = p.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + + typeSpec := p.TypeDefinitions["api"]["Parent"] + err = p.ParseDefinition("api", typeSpec.Name.Name, typeSpec) + assert.NoError(t, err) + + out, err := json.MarshalIndent(p.swagger.Definitions, "", " ") + assert.NoError(t, err) + assert.Equal(t, expected, string(out)) +} + func TestParser_ParseStructMapMember(t *testing.T) { src := ` package api @@ -2392,14 +2454,14 @@ type Child struct { } type Parent struct { - Test1 map[string]interface{} - Test2 map[string]string - Test3 map[string]*string - Test4 map[string]Child - Test5 map[string]*Child - Test6 MyMapType - Test7 []Child - Test8 []*Child + Test1 map[string]interface{} //test1 + Test2 map[string]string //test2 + Test3 map[string]*string //test3 + Test4 map[string]Child //test4 + Test5 map[string]*Child //test5 + Test6 MyMapType //test6 + Test7 []Child //test7 + Test8 []*Child //test8 } // @Success 200 {object} Parent @@ -2426,44 +2488,52 @@ func Test(){ "type": "object", "properties": { "test1": { + "description": "test1", "type": "object", "additionalProperties": true }, "test2": { + "description": "test2", "type": "object", "additionalProperties": { "type": "string" } }, "test3": { + "description": "test3", "type": "object", "additionalProperties": { "type": "string" } }, "test4": { + "description": "test4", "type": "object", "additionalProperties": { "$ref": "#/definitions/api.Child" } }, "test5": { + "description": "test5", "type": "object", "additionalProperties": { "$ref": "#/definitions/api.Child" } }, "test6": { + "description": "test6", "type": "object", "$ref": "#/definitions/api.MyMapType" }, "test7": { + "description": "test7", "type": "array", "items": { "$ref": "#/definitions/api.Child" } }, "test8": { + "description": "test8", "type": "array", "items": { "$ref": "#/definitions/api.Child" @@ -2485,10 +2555,8 @@ func Test(){ assert.NoError(t, err) out, err := json.MarshalIndent(p.swagger.Definitions, "", " ") - assert.NoError(t, err) assert.Equal(t, expected, string(out)) - } func TestParser_ParseRouterApiInfoErr(t *testing.T) { From ed6cb869b80de1e2bd874802c90adba00a3b6ac0 Mon Sep 17 00:00:00 2001 From: chengjin Date: Sat, 14 Dec 2019 17:53:05 +0800 Subject: [PATCH 4/4] remove GOOS --- parser_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/parser_test.go b/parser_test.go index 89b462178..953e06e52 100644 --- a/parser_test.go +++ b/parser_test.go @@ -8,7 +8,6 @@ import ( "os" "path" "path/filepath" - "runtime" "testing" "github.com/stretchr/testify/assert" @@ -254,13 +253,8 @@ func TestGetAllGoFileInfo(t *testing.T) { err := p.getAllGoFileInfo(searchDir) assert.NoError(t, err) - if runtime.GOOS == "windows" { - assert.NotEmpty(t, p.files["testdata\\pet\\main.go"]) - assert.NotEmpty(t, p.files["testdata\\pet\\web\\handler.go"]) - } else { - assert.NotEmpty(t, p.files["testdata/pet/main.go"]) - assert.NotEmpty(t, p.files["testdata/pet/web/handler.go"]) - } + assert.NotEmpty(t, p.files["testdata/pet/main.go"]) + assert.NotEmpty(t, p.files["testdata/pet/web/handler.go"]) assert.Equal(t, 2, len(p.files)) }