From a13bcf856493d162d98111a678b22dab9c8e60ab Mon Sep 17 00:00:00 2001 From: chengjin Date: Sat, 28 Mar 2020 17:34:41 +0800 Subject: [PATCH] enhance issue #650, PR #651 --- README.md | 5 +++ operation.go | 91 +++++++++++++++++++++++++---------------------- operation_test.go | 85 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index ec6ed4967..4fb00cf6a 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,7 @@ type Account struct { ### Model composition in response ```go +// JSONResult's data field will be overridden by the specific type proto.Order @success 200 {object} jsonresult.JSONResult{data=proto.Order} "desc" ``` @@ -526,6 +527,10 @@ type Order struct { //in `proto` package @success 200 {object} jsonresult.JSONResult{data=[]string} "desc" ``` +- overriding multiple fields. field will be added if not exists +```go +@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc" +``` ### Add a headers in response diff --git a/operation.go b/operation.go index cf3152c73..fcb7f3eac 100644 --- a/operation.go +++ b/operation.go @@ -630,7 +630,7 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) { return nil, fmt.Errorf("type spec not found") } -var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=\[\]]+)[^"]*(.*)?`) +var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=,\[\]]+)[^"]*(.*)?`) type nestedField struct { Name string @@ -640,57 +640,45 @@ type nestedField struct { } func (nested *nestedField) getSchema() *spec.Schema { - if IsGolangPrimitiveType(nested.Type) { + if IsPrimitiveType(nested.Type) { return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}} } return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}} } -func (nested *nestedField) fillNestedSchema(response *spec.Response, ref spec.Ref) { - props := make(map[string]spec.Schema, 0) - if nested.IsArray { - props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, - }} - } else { - props[nested.Name] = *nested.getSchema() - } - nestedSpec := spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: props, - }, - } - response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} -} - -var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`) -var nestedArrayPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=\[\]([^\[\]]*)\}$`) +var nestedPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)\}$`) +var nestedObjectPattern = regexp.MustCompile(`^(.*)=(.*)$`) -func (operation *Operation) tryExtractNestedField(specStr string, astFile *ast.File) (refType string, nested *nestedField, err error) { - if matches := nestedObjectPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: false} - } else if matches := nestedArrayPattern.FindStringSubmatch(specStr); len(matches) == 4 { - refType, nested = matches[1], &nestedField{Name: matches[2], Type: matches[3], IsArray: true} - } else { +func (operation *Operation) tryExtractNestedFields(specStr string, astFile *ast.File) (refType string, nestedFields []*nestedField, err error) { + if matches := nestedPattern.FindStringSubmatch(specStr); len(matches) != 3 { return specStr, nil, nil - } - - if !IsGolangPrimitiveType(nested.Type) { - if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' - refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) - if err != nil { - return specStr, nil, err - } + } else { + refType = matches[1] + fields := strings.Split(matches[2], ",") + for _, field := range fields { + if matches := nestedObjectPattern.FindStringSubmatch(field); len(matches) == 3 { + nested := &nestedField{Name: matches[1], Type: matches[2], IsArray: strings.HasPrefix(matches[2], "[]")} + if nested.IsArray { + nested.Type = nested.Type[2:] + } + nested.Type = TransToValidSchemeType(nested.Type) + if !IsPrimitiveType(nested.Type) { + if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' + refType, typeSpec, err := operation.registerSchemaType(nested.Type, astFile) + if err != nil { + return specStr, nil, err + } - nested.Ref = spec.Ref{ - Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + nested.Ref = spec.Ref{ + Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), + } + } + } + nestedFields = append(nestedFields, nested) } } } - return } @@ -719,7 +707,7 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as schemaType := strings.Trim(matches[2], "{}") refType := matches[3] - refType, nested, err := operation.tryExtractNestedField(refType, astFile) + refType, nestedFields, err := operation.tryExtractNestedFields(refType, astFile) if err != nil { return err } @@ -743,10 +731,27 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)), } - if nested == nil { + if nestedFields == nil { response.Schema.Ref = ref } else { - nested.fillNestedSchema(&response, ref) + props := make(map[string]spec.Schema) + for _, nested := range nestedFields { + if nested.IsArray { + props[nested.Name] = spec.Schema{SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{Schema: nested.getSchema()}, + }} + } else { + props[nested.Name] = *nested.getSchema() + } + } + nestedSpec := spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: props, + }, + } + response.Schema.AllOf = []spec.Schema{{SchemaProps: spec.SchemaProps{Ref: ref}}, nestedSpec} } } else if schemaType == "array" { diff --git a/operation_test.go b/operation_test.go index f9c339260..c85cc5584 100644 --- a/operation_test.go +++ b/operation_test.go @@ -189,7 +189,7 @@ func TestParseResponseCommentWithObjectType(t *testing.T) { } func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=string} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=string,data2=int} "Error message, if code != 200` operation := NewOperation() operation.parser = New() @@ -218,6 +218,9 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { "properties": { "data": { "type": "string" + }, + "data2": { + "type": "integer" } } } @@ -230,7 +233,7 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { } func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=[]string} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=[]string,data2=[]int} "Error message, if code != 200` operation := NewOperation() operation.parser = New() @@ -262,6 +265,12 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { "items": { "type": "string" } + }, + "data2": { + "type": "array", + "items": { + "type": "integer" + } } } } @@ -274,13 +283,14 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { } func TestParseResponseCommentWithNestedObjectType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=model.Payload} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=model.Payload,data2=model.Payload2} "Error message, if code != 200` operation := NewOperation() operation.parser = New() operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload2"] = &ast.TypeSpec{} err := operation.ParseComment(comment, nil) assert.NoError(t, err) @@ -304,6 +314,9 @@ func TestParseResponseCommentWithNestedObjectType(t *testing.T) { "properties": { "data": { "$ref": "#/definitions/model.Payload" + }, + "data2": { + "$ref": "#/definitions/model.Payload2" } } } @@ -316,13 +329,14 @@ func TestParseResponseCommentWithNestedObjectType(t *testing.T) { } func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { - comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload} "Error message, if code != 200` + comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload,data2=[]model.Payload2} "Error message, if code != 200` operation := NewOperation() operation.parser = New() operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload2"] = &ast.TypeSpec{} err := operation.ParseComment(comment, nil) assert.NoError(t, err) @@ -349,6 +363,69 @@ func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { "items": { "$ref": "#/definitions/model.Payload" } + }, + "data2": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Payload2" + } + } + } + } + ] + } + } + } +}` + assert.Equal(t, expected, string(b)) +} + +func TestParseResponseCommentWithNestedFields(t *testing.T) { + comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload,data4=[]model.Payload} "Error message, if code != 200` + operation := NewOperation() + operation.parser = New() + + operation.parser.TypeDefinitions["model"] = make(map[string]*ast.TypeSpec) + operation.parser.TypeDefinitions["model"]["CommonHeader"] = &ast.TypeSpec{} + operation.parser.TypeDefinitions["model"]["Payload"] = &ast.TypeSpec{} + + err := operation.ParseComment(comment, nil) + assert.NoError(t, err) + + response := operation.Responses.StatusCodeResponses[200] + assert.Equal(t, `Error message, if code != 200`, response.Description) + + b, _ := json.MarshalIndent(operation, "", " ") + + expected := `{ + "responses": { + "200": { + "description": "Error message, if code != 200", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/model.CommonHeader" + }, + { + "type": "object", + "properties": { + "data1": { + "type": "integer" + }, + "data2": { + "type": "array", + "items": { + "type": "integer" + } + }, + "data3": { + "$ref": "#/definitions/model.Payload" + }, + "data4": { + "type": "array", + "items": { + "$ref": "#/definitions/model.Payload" + } } } }