Skip to content

Commit

Permalink
Support Model Composition in Response with allOf and ref
Browse files Browse the repository at this point in the history
  • Loading branch information
whunmr committed Mar 22, 2020
1 parent 8e8a052 commit ecf0baa
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 2 deletions.
81 changes: 79 additions & 2 deletions operation.go
Expand Up @@ -610,7 +610,72 @@ 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
Type string
IsArray bool

Ref spec.Ref
}

func (nested *NestedField) GetSchema() *spec.Schema{
if IsGolangPrimitiveType(nested.Type) {
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}}
} else {
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 = make([]spec.Schema, 0)
response.Schema.AllOf = append(response.Schema.AllOf, spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}})
response.Schema.AllOf = append(response.Schema.AllOf, nestedSpec)
}

var nestedObjectPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=([^\[\]]*)\}$`)
var nestedArrayPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)=\[\]([^\[\]]*)\}$`)

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 {
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
}

nested.Ref = spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)),
}
}
}

return
}

// ParseResponseComment parses comment for given `response` comment string.
func (operation *Operation) ParseResponseComment(commentLine string, astFile *ast.File) error {
Expand All @@ -637,6 +702,11 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
schemaType := strings.Trim(matches[2], "{}")
refType := matches[3]

refType, nested, err := operation.tryExtractNestedField(refType, astFile)
if err != nil {
return err
}

var typeSpec *ast.TypeSpec
if !IsGolangPrimitiveType(refType) {
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
Expand All @@ -652,9 +722,16 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as

if schemaType == "object" {
response.Schema.SchemaProps = spec.SchemaProps{}
response.Schema.Ref = spec.Ref{
ref := spec.Ref {
Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)),
}

if nested == nil {
response.Schema.Ref = ref
} else {
nested.FillNestedSchema(response, ref)
}

} else if schemaType == "array" {
refType = TransToValidSchemeType(refType)
if IsPrimitiveType(refType) {
Expand Down
174 changes: 174 additions & 0 deletions operation_test.go
Expand Up @@ -188,6 +188,180 @@ func TestParseResponseCommentWithObjectType(t *testing.T) {
assert.Equal(t, expected, string(b))
}

func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) {
comment := `@Success 200 {object} model.CommonHeader{data=string} "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{}

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": {
"data": {
"type": "string"
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}


func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) {
comment := `@Success 200 {object} model.CommonHeader{data=[]string} "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{}

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": {
"data": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}


func TestParseResponseCommentWithNestedObjectType(t *testing.T) {
comment := `@Success 200 {object} model.CommonHeader{data=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": {
"data": {
"$ref": "#/definitions/model.Payload"
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}

func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) {
comment := `@Success 200 {object} model.CommonHeader{data=[]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": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Payload"
}
}
}
}
]
}
}
}
}`
assert.Equal(t, expected, string(b))
}

func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) {
comment := `@Success 200 {object} testOwner "Error message, if code != 200"`
operation := NewOperation()
Expand Down

0 comments on commit ecf0baa

Please sign in to comment.