From 4e3f6b61a59370a443ba97af948afc7f15e7a239 Mon Sep 17 00:00:00 2001 From: alovn Date: Mon, 9 May 2022 17:57:27 +0800 Subject: [PATCH] feat: response map --- .vscode/launch.json | 15 ++ examples/docs/apis-greeter.md | 25 +++- examples/main.go | 14 +- operation.go | 4 +- parser.go | 265 ++++++++++++++++++++-------------- schema.go | 3 +- spec.go | 2 +- 7 files changed, 210 insertions(+), 118 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a2eaa86 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/apidoc/main.go" + } + ] +} \ No newline at end of file diff --git a/examples/docs/apis-greeter.md b/examples/docs/apis-greeter.md index b1b8414..ff71b8f 100644 --- a/examples/docs/apis-greeter.md +++ b/examples/docs/apis-greeter.md @@ -19,10 +19,31 @@ GET /greeter { //object(main.Response), 通用返回结果 "code": 0, //int, 返回状态码 "data": { //object(main.TestData) - "MyInt": 123, //int + "Map2": { //object(main.TestData2) + "abc": null //object + }, + "Map3": { //object(main.TestData2) + "abc": { //object(main.TestData2) + "MyAge2": 123, //int + "MyTitle2": "abc" //string, 标题2 + } + }, "MyInts": [ //array[int] 123 - ] + ], + "Nodes": { //object(main.Node) + "abc": { //object(main.Node) + "Name": "abc", //string + "Nodes": { //object(main.Node) + "abc": null //object + } + } + }, + "amap": , //object + "data2": { //object(main.TestData2) + "MyAge2": 123, //int + "MyTitle2": "abc" //string, 标题2 + } }, "msg": "返回消息" //string, 返回文本消息 } diff --git a/examples/main.go b/examples/main.go index 1498ff7..a132b49 100644 --- a/examples/main.go +++ b/examples/main.go @@ -32,17 +32,27 @@ type TestData2 struct { MyTitle2 string //标题2 MyAge2 int } +type Map map[string]interface{} +type Map2 map[string]TestData2 +type Node struct { + Name string + Nodes map[string]Node +} type TestData struct { // MyTitle string `json:"my_title,omitempty"` //标题 - // Data2 *TestData2 `json:"data2,omitempty"` + Data2 *TestData2 `json:"data2,omitempty"` // MyIntData int // MyFloat64 float64 // MyFloat32 float32 // MyIntArray []int // MyTestData2Array []TestData2 // Int *int - MyInt MyInt + // MyInt MyInt MyInts []MyInt + Map Map `json:"amap"` + Map2 Map2 + Map3 map[string]TestData2 + Nodes map[string]Node } type Request struct { diff --git a/operation.go b/operation.go index f342d35..78e6dcd 100644 --- a/operation.go +++ b/operation.go @@ -127,7 +127,7 @@ func (operation *Operation) ParseRequestComment(commentLine string, astFile *ast case IsGolangPrimitiveType(refType): return nil default: - schema, err := operation.parser.getTypeSchema(refType, astFile, nil, true) + schema, err := operation.parser.getTypeSchema(refType, astFile, nil) if err != nil { return err } @@ -205,7 +205,7 @@ func (operation *Operation) parseObject(refType string, astFile *ast.File) (*Typ case strings.Contains(refType, "{"): return operation.parseCombinedObject(refType, astFile) default: - schema, err := operation.parser.getTypeSchema(refType, astFile, nil, true) + schema, err := operation.parser.getTypeSchema(refType, astFile, nil) if err != nil { return nil, err } diff --git a/parser.go b/parser.go index 0caa9e8..0249939 100644 --- a/parser.go +++ b/parser.go @@ -352,7 +352,7 @@ func fullTypeName(pkgName, typeName string) string { return typeName } -func (parser *Parser) getTypeSchema(typeName string, file *ast.File, field *ast.Field, ref bool) (*TypeSchema, error) { +func (parser *Parser) getTypeSchema(typeName string, file *ast.File, field *ast.Field) (*TypeSchema, error) { if IsGolangPrimitiveType(typeName) { name := field.Names[0].Name isOmitempty, fieldName := getFieldName(name, field, "json") @@ -413,17 +413,24 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef, field *ast.Field if parser.isInStructStack(typeSpecDef) { fmt.Printf("Skipping '%s', recursion detected.", typeName) - return &TypeSchema{ - Name: refTypeName, - Type: OBJECT, - PkgPath: typeSpecDef.PkgPath, - }, nil + schema := &TypeSchema{ + Name: refTypeName, + FieldName: refTypeName, + FullName: typeName, + Type: OBJECT, + Example: NULL, + PkgPath: typeSpecDef.PkgPath, + } + parser.clearStructStack() + return schema, nil } parser.structStack = append(parser.structStack, typeSpecDef) fmt.Printf("Generating %s\n", typeName) + // return parser.parseTypeExpr(typeSpecDef.File, field, typeSpecDef.TypeSpec.Type) + switch expr := typeSpecDef.TypeSpec.Type.(type) { // type Foo struct {...} case *ast.StructType: @@ -435,8 +442,30 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef, field *ast.Field schema.FullName = typeSpecDef.FullName() return schema, err case *ast.Ident: - fmt.Println("myint:", expr.Name) - return parser.getTypeSchema(expr.Name, typeSpecDef.File, field, false) + return parser.getTypeSchema(expr.Name, typeSpecDef.File, field) + case *ast.MapType: + if keyIdent, ok := expr.Key.(*ast.Ident); ok { + if IsGolangPrimitiveType(keyIdent.Name) { + example := getFieldExample(keyIdent.Name, nil) + if _, ok := expr.Value.(*ast.InterfaceType); ok { + return &TypeSchema{Type: OBJECT, Properties: nil}, nil + } + schema, err := parser.parseTypeExpr(typeSpecDef.File, field, expr.Value) + if err != nil { + return nil, err + } + return &TypeSchema{ + Name: example, + Type: OBJECT, + FieldName: example, + FullName: schema.FullName, + Properties: map[string]*TypeSchema{ + strings.Trim(example, "\""): schema, + }, + }, err + } + } + default: fmt.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeSpecDef.TypeSpec.Type) } @@ -450,60 +479,76 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef, field *ast.Field return &sch, nil } -// func (parser *Parser) parseTypeExpr(file *ast.File, field *ast.Field, typeExpr ast.Expr, ref bool) (*TypeSchema, error) { -// switch expr := typeExpr.(type) { -// // type Foo interface{} -// case *ast.InterfaceType: -// return &TypeSchema{}, nil +func (parser *Parser) parseTypeExpr(file *ast.File, field *ast.Field, typeExpr ast.Expr) (*TypeSchema, error) { + switch expr := typeExpr.(type) { + // type Foo interface{} + case *ast.InterfaceType: + return &TypeSchema{ + Type: ANY, + Example: "null", + }, nil -// // type Foo struct {...} -// case *ast.StructType: -// return parser.parseStruct(file, expr.Fields) + // type Foo struct {...} + case *ast.StructType: + return parser.parseStruct(file, expr.Fields) -// // type Foo Baz -// case *ast.Ident: -// return parser.getTypeSchema(expr.Name, file, field, ref) + // type Foo Baz + case *ast.Ident: + return parser.getTypeSchema(expr.Name, file, field) -// // type Foo *Baz -// case *ast.StarExpr: -// return parser.parseTypeExpr(file, field, expr.X, ref) + // type Foo *Baz + case *ast.StarExpr: + return parser.parseTypeExpr(file, field, expr.X) -// // type Foo pkg.Bar -// case *ast.SelectorExpr: -// if xIdent, ok := expr.X.(*ast.Ident); ok { -// return parser.getTypeSchema(fullTypeName(xIdent.Name, expr.Sel.Name), file, field, ref) -// } -// // type Foo []Baz -// case *ast.ArrayType: -// itemSchema, err := parser.parseTypeExpr(file, field, expr.Elt, true) -// if err != nil { -// return nil, err -// } -// return &TypeSchema{Type: "array", ArraySchema: itemSchema}, nil -// // type Foo map[string]Bar -// // case *ast.MapType: -// // if _, ok := expr.Value.(*ast.InterfaceType); ok { -// // return &TypeSchema{Type: OBJECT, Properties: nil}, nil -// // } -// // schema, err := parser.parseTypeExpr(file, expr.Value, true) -// // if err != nil { -// // return nil, err -// // } - -// // return spec.MapProperty(schema), nil - -// // case *ast.FuncType: -// // return nil, ErrFuncTypeField -// // ... -// default: -// fmt.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr) -// } + // type Foo pkg.Bar + case *ast.SelectorExpr: + if xIdent, ok := expr.X.(*ast.Ident); ok { + return parser.getTypeSchema(fullTypeName(xIdent.Name, expr.Sel.Name), file, field) + } + // type Foo []Baz + case *ast.ArrayType: + itemSchema, err := parser.parseTypeExpr(file, field, expr.Elt) + if err != nil { + return nil, err + } + return &TypeSchema{Type: "array", IsArray: true, ArraySchema: itemSchema}, nil + // type Foo map[string]Bar + case *ast.MapType: + if keyIdent, ok := expr.Key.(*ast.Ident); ok { + if IsGolangPrimitiveType(keyIdent.Name) { + example := getFieldExample(keyIdent.Name, nil) + if _, ok := expr.Value.(*ast.InterfaceType); ok { + return &TypeSchema{Type: OBJECT, Properties: nil}, nil + } + schema, err := parser.parseTypeExpr(file, field, expr.Value) + if err != nil { + return nil, err + } + return &TypeSchema{ + Name: example, + Type: OBJECT, + FieldName: example, + FullName: schema.FullName, + Properties: map[string]*TypeSchema{ + strings.Trim(example, "\""): schema, + }, + }, err + } + } -// return &TypeSchema{Type: OBJECT}, nil -// } + // case *ast.FuncType: + // return nil, ErrFuncTypeField + // ... + default: + fmt.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr) + } + + return &TypeSchema{Type: OBJECT}, nil +} func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*TypeSchema, error) { properties := make(map[string]*TypeSchema) + // parser.clearStructStack() //warning for _, field := range fields.List { if len(field.Names) != 1 { return nil, errors.New("error len(field.Names) != 1") @@ -531,63 +576,63 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (*TypeS if !ast.IsExported(name) { return nil, nil } - - isArray, typeName, err := getFieldType(field.Type) - if err != nil { - return nil, err - } - if isArray { - parser.clearStructStack() //warning - schema, err := parser.getTypeSchema(typeName, file, field, false) - if err != nil { - return nil, err - } - fmt.Println("arrayschema", schema.Name) - return &TypeSchema{ - IsArray: isArray, - Type: ARRAY, - ArraySchema: schema, - }, nil - // return schema, nil - } else { - schema, err := parser.getTypeSchema(typeName, file, field, false) - if err != nil { - return nil, err - } - return schema, nil - } + return parser.parseTypeExpr(file, field, field.Type) + // isArray, typeName, err := getFieldType(field.Type) + // if err != nil { + // return nil, err + // } + // if isArray { + // parser.clearStructStack() //warning + // schema, err := parser.getTypeSchema(typeName, file, field, false) + // if err != nil { + // return nil, err + // } + // fmt.Println("arrayschema", schema.Name) + // return &TypeSchema{ + // IsArray: isArray, + // Type: ARRAY, + // ArraySchema: schema, + // }, nil + // // return schema, nil + // } else { + // schema, err := parser.getTypeSchema(typeName, file, field, false) + // if err != nil { + // return nil, err + // } + // return schema, nil + // } } -func getFieldType(field ast.Expr) (isArray bool, typeName string, err error) { - switch fieldType := field.(type) { - case *ast.Ident: - return false, fieldType.Name, nil - case *ast.SelectorExpr: - isArray, packageName, err := getFieldType(fieldType.X) - if err != nil { - return false, "", err - } +// func getFieldType(field ast.Expr) (isArray bool, typeName string, err error) { +// switch fieldType := field.(type) { +// case *ast.Ident: +// return false, fieldType.Name, nil +// case *ast.SelectorExpr: +// isArray, packageName, err := getFieldType(fieldType.X) +// if err != nil { +// return false, "", err +// } - return isArray, fullTypeName(packageName, fieldType.Sel.Name), nil - case *ast.StarExpr: - isArray, fullName, err := getFieldType(fieldType.X) - if err != nil { - return false, "", err - } +// return isArray, fullTypeName(packageName, fieldType.Sel.Name), nil +// case *ast.StarExpr: +// isArray, fullName, err := getFieldType(fieldType.X) +// if err != nil { +// return false, "", err +// } - return isArray, fullName, nil - case *ast.InterfaceType: - return false, ANY, nil - case *ast.ArrayType: - isArray, arrayType, err := getFieldType(fieldType.Elt) - if err != nil { - return false, "", err - } - if isArray { - return true, arrayType, fmt.Errorf("unsurport field type [][]" + arrayType) - } - return true, arrayType, nil - default: - return false, "", fmt.Errorf("unknown field type %#v", field) - } -} +// return isArray, fullName, nil +// case *ast.InterfaceType: +// return false, ANY, nil +// case *ast.ArrayType: +// isArray, arrayType, err := getFieldType(fieldType.Elt) +// if err != nil { +// return false, "", err +// } +// if isArray { +// return true, arrayType, fmt.Errorf("unsurport field type [][]" + arrayType) +// } +// return true, arrayType, nil +// default: +// return false, "", fmt.Errorf("unknown field type %#v", field) +// } +// } diff --git a/schema.go b/schema.go index b42706d..2fe9c4d 100644 --- a/schema.go +++ b/schema.go @@ -28,7 +28,8 @@ const ( // ANY represent a any value. ANY = "any" // NIL represent a empty value. - NIL = "nil" + NIL = "nil" + NULL = "null" ) // CheckSchemaType checks if typeName is not a name of primitive type. diff --git a/spec.go b/spec.go index 140c2d7..952f2c9 100644 --- a/spec.go +++ b/spec.go @@ -264,7 +264,7 @@ func exampleBool(example string) bool { func exampleString(example string) string { if example == "" { - return "example" + return "abc" } return example }