Skip to content

Commit

Permalink
enhance issue #650, PR #651
Browse files Browse the repository at this point in the history
  • Loading branch information
sdghchj committed Mar 28, 2020
1 parent 8e21f4c commit a13bcf8
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 47 deletions.
5 changes: 5 additions & 0 deletions README.md
Expand Up @@ -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"
```

Expand All @@ -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

Expand Down
91 changes: 48 additions & 43 deletions operation.go
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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" {
Expand Down
85 changes: 81 additions & 4 deletions operation_test.go
Expand Up @@ -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()

Expand Down Expand Up @@ -218,6 +218,9 @@ func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) {
"properties": {
"data": {
"type": "string"
},
"data2": {
"type": "integer"
}
}
}
Expand All @@ -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()

Expand Down Expand Up @@ -262,6 +265,12 @@ func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) {
"items": {
"type": "string"
}
},
"data2": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
Expand All @@ -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)
Expand All @@ -304,6 +314,9 @@ func TestParseResponseCommentWithNestedObjectType(t *testing.T) {
"properties": {
"data": {
"$ref": "#/definitions/model.Payload"
},
"data2": {
"$ref": "#/definitions/model.Payload2"
}
}
}
Expand All @@ -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)
Expand All @@ -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"
}
}
}
}
Expand Down

0 comments on commit a13bcf8

Please sign in to comment.