Skip to content

Commit

Permalink
Merge branch 'master' into fixed-query-order
Browse files Browse the repository at this point in the history
  • Loading branch information
easonlin404 committed Apr 20, 2020
2 parents 719cfd8 + 593555a commit 2b0d62a
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 141 deletions.
17 changes: 12 additions & 5 deletions README.md
Expand Up @@ -510,13 +510,14 @@ type Account struct {

```go
type JSONResult struct {
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
Code int `json:"code" `
Message string `json:"message"`
Data interface{} `json:"data"`
}

type Order struct { //in `proto` package
...
Id uint `json:"id"`
Data interface{} `json:"data"`
}
```

Expand All @@ -531,7 +532,13 @@ type Order struct { //in `proto` package
```go
@success 200 {object} jsonresult.JSONResult{data1=string,data2=[]string,data3=proto.Order,data4=[]proto.Order} "desc"
```

- overriding deep-level fields
```go
type DeepObject struct { //in `proto` package
...
}
@success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
```
### Add a headers in response

```go
Expand Down
242 changes: 137 additions & 105 deletions operation.go
Expand Up @@ -639,54 +639,152 @@ func findTypeDef(importPath, typeName string) (*ast.TypeSpec, error) {

var responsePattern = regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\{\}=,\[\]]+)[^"]*(.*)?`)

type nestedField struct {
Name string
Type string
IsArray bool
Ref spec.Ref
}
//RepsonseType{data1=Type1,data2=Type2}
var combinedPattern = regexp.MustCompile(`^([\w\-\.\/\[\]]+)\{(.*)\}$`)

func (nested *nestedField) getSchema() *spec.Schema {
if IsPrimitiveType(nested.Type) {
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{nested.Type}}}
func (operation *Operation) parseResponseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
switch {
case refType == "interface{}":
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}}}, nil
case IsGolangPrimitiveType(refType):
refType = TransToValidSchemeType(refType)
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}, nil
case IsPrimitiveType(refType):
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{refType}}}, nil
case strings.HasPrefix(refType, "[]"):
schema, err := operation.parseResponseObjectSchema(refType[2:], astFile)
if err != nil {
return nil, err
}
return &spec.Schema{SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{Schema: schema}},
}, nil
case strings.HasPrefix(refType, "map["):
//ignore key type
idx := strings.Index(refType, "]")
if idx < 0 {
return nil, fmt.Errorf("invalid type: %s", refType)
}
refType = refType[idx+1:]
var valueSchema spec.SchemaOrBool
if refType == "interface{}" {
valueSchema.Allows = true
} else {
schema, err := operation.parseResponseObjectSchema(refType, astFile)
if err != nil {
return &spec.Schema{}, err
}
valueSchema.Schema = schema
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
AdditionalProperties: &valueSchema,
},
}, nil
case strings.Contains(refType, "{"):
return operation.parseResponseCombinedObjectSchema(refType, astFile)
default:
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
refNewType, typeSpec, err := operation.registerSchemaType(refType, astFile)
if err != nil {
return nil, err
}
refType = TypeDocName(refNewType, typeSpec)
}
return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + refType),
}}}, nil
}

return &spec.Schema{SchemaProps: spec.SchemaProps{Ref: nested.Ref}}
}

//RepsonseType{data1=Type1,data2=Type2}
var nestedPattern = regexp.MustCompile(`^([\w\-\.\/]+)\{(.*)\}$`)

func (operation *Operation) tryExtractNestedFields(specStr string, astFile *ast.File) (refType string, nestedFields []*nestedField, err error) {
matches := nestedPattern.FindStringSubmatch(specStr)
func (operation *Operation) parseResponseCombinedObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
matches := combinedPattern.FindStringSubmatch(refType)
if len(matches) != 3 {
return specStr, nil, nil
return nil, fmt.Errorf("invalid type: %s", refType)
}
refType = matches[1]
fields := strings.Split(matches[2], ",")
for _, field := range fields {
if matches := strings.Split(field, "="); len(matches) == 2 {
nested := &nestedField{Name: matches[0], Type: matches[1], IsArray: strings.HasPrefix(matches[1], "[]")}
if nested.IsArray {
nested.Type = nested.Type[2:]
schema, err := operation.parseResponseObjectSchema(refType, astFile)
if err != nil {
return nil, err
}

parseFields := func(s string) []string {
n := 0
return strings.FieldsFunc(s, func(r rune) bool {
if r == '{' {
n++
return false
} else if r == '}' {
n--
return false
}
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
}
return r == ',' && n == 0
})
}

nested.Ref = spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec)),
}
fields := parseFields(matches[2])
props := map[string]spec.Schema{}
for _, field := range fields {
if matches := strings.SplitN(field, "=", 2); len(matches) == 2 {
if strings.HasPrefix(matches[1], "[]") {
itemSchema, err := operation.parseResponseObjectSchema(matches[1][2:], astFile)
if err != nil {
return nil, err
}
props[matches[0]] = spec.Schema{SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{Schema: itemSchema}},
}
} else {
schema, err := operation.parseResponseObjectSchema(matches[1], astFile)
if err != nil {
return nil, err
}
props[matches[0]] = *schema
}
nestedFields = append(nestedFields, nested)
}
}
return

if len(props) == 0 {
return schema, nil
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
AllOf: []spec.Schema{
*schema,
{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: props,
},
},
},
},
}, nil
}

func (operation *Operation) parseResponseSchema(schemaType, refType string, astFile *ast.File) (*spec.Schema, error) {
switch schemaType {
case "object":
if !strings.HasPrefix(refType, "[]") {
return operation.parseResponseObjectSchema(refType, astFile)
}
refType = refType[2:]
fallthrough
case "array":
schema, err := operation.parseResponseObjectSchema(refType, astFile)
if err != nil {
return nil, err
}
return &spec.Schema{SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{Schema: schema}},
}, nil
default:
return &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}}, nil
}
}

// ParseResponseComment parses comment for given `response` comment string.
Expand All @@ -701,87 +799,20 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
return err
}

response := spec.Response{}

code, _ := strconv.Atoi(matches[1])

responseDescription := strings.Trim(matches[4], "\"")
if responseDescription == "" {
responseDescription = http.StatusText(code)
}
response.Description = responseDescription

schemaType := strings.Trim(matches[2], "{}")
refType := matches[3]

refType, nestedFields, err := operation.tryExtractNestedFields(refType, astFile)
schema, err := operation.parseResponseSchema(schemaType, refType, astFile)
if err != nil {
return err
}

var typeSpec *ast.TypeSpec
if !IsGolangPrimitiveType(refType) {
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
var err error
if refType, typeSpec, err = operation.registerSchemaType(refType, astFile); err != nil {
return err
}
}
}

// so we have to know all type in app
response.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{schemaType}}}

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

if nestedFields == nil {
response.Schema.Ref = ref
} else {
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" {
refType = TransToValidSchemeType(refType)
if IsPrimitiveType(refType) {
response.Schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{refType},
},
},
}
} else {
response.Schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + TypeDocName(refType, typeSpec))},
},
},
}
}
}

if operation.Responses == nil {
operation.Responses = &spec.Responses{
ResponsesProps: spec.ResponsesProps{
Expand All @@ -790,8 +821,9 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
}
}

operation.Responses.StatusCodeResponses[code] = response

operation.Responses.StatusCodeResponses[code] = spec.Response{
ResponseProps: spec.ResponseProps{Schema: schema, Description: responseDescription},
}
return nil
}

Expand Down

0 comments on commit 2b0d62a

Please sign in to comment.