Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 207 additions & 1 deletion oas.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package main

import "github.com/iancoleman/orderedmap"
import (
"fmt"
"strings"

"github.com/iancoleman/orderedmap"
)

const (
OpenAPIVersion = "3.0.0"
Expand Down Expand Up @@ -191,6 +196,207 @@ type ResponseObject struct {
// Links
}

type ResponseObjectPackage struct {
pkgPath string
pkgName string
jsonType string
goType string
desc string
p *parser
RO *ResponseObject
handlerError error
}

func newMediaTypeObjectCustomSchema(schema SchemaObject) *MediaTypeObject {
return &MediaTypeObject{
Schema: schema,
}
}

func newMediaTypeObjectCustomRef(typeName string) *MediaTypeObject {
return &MediaTypeObject{
Schema: SchemaObject{
Ref: addSchemaRefLinkPrefix(typeName),
},
}
}

func newMediaTypeObjectCustomType(mediaType string) *MediaTypeObject {
return &MediaTypeObject{
Schema: SchemaObject{
Type: mediaType,
},
}
}

type ResponseObjectHandler interface {
execute(*ResponseObjectPackage)
setNext(ResponseObjectHandler)
}

type EmptyType struct {
next ResponseObjectHandler
}

type TerminalMediaType struct {
next ResponseObjectHandler
}
type RefType struct {
next ResponseObjectHandler
}

type ComplexGoType struct {
next ResponseObjectHandler
}

type SimpleGoType struct {
next ResponseObjectHandler
}

type FailType struct {
next ResponseObjectHandler
}

func (mt *EmptyType) execute(ROP *ResponseObjectPackage) {
if ROP.jsonType != "" {
mt.next.execute(ROP)
}
}

func (mt *EmptyType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func (mt *FailType) execute(ROP *ResponseObjectPackage) {
ROP.handlerError = fmt.Errorf("getResponseObject - unknown json type %v", ROP.jsonType)
}

func (mt *FailType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func (mt *TerminalMediaType) execute(ROP *ResponseObjectPackage) {
if isTerminal(ROP.jsonType) {
ROP.RO.Content[ContentTypeJson] = newMediaTypeObjectCustomType(cleanBrackets(ROP.jsonType))
return
}
mt.next.execute(ROP)
}

func (mt *TerminalMediaType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func (mt *ComplexGoType) execute(ROP *ResponseObjectPackage) {
if isComplex(ROP.jsonType) && isComplexGoType(ROP.goType) {
schema, err := ROP.p.parseSchemaObject(ROP.pkgPath, ROP.pkgName, ROP.goType)
if err != nil {
ROP.p.debug("parseResponseComment: cannot parse goType", ROP.goType)
}
ROP.RO.Content[ContentTypeJson] = newMediaTypeObjectCustomSchema(*schema)
return
}
mt.next.execute(ROP)

}
func (mt *ComplexGoType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func (mt *SimpleGoType) execute(ROP *ResponseObjectPackage) {
typeName, err := ROP.p.registerType(ROP.pkgPath, ROP.pkgName, ROP.goType)
if err != nil {
ROP.handlerError = err
return
}

if isComplex(ROP.jsonType) && isBasicGoType(typeName) {
ROP.RO.Content[ContentTypeText] = newMediaTypeObjectCustomType("string")
return
}

mt.next.execute(ROP)

}

func (mt *SimpleGoType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func (mt *RefType) execute(ROP *ResponseObjectPackage) {
typeName, err := ROP.p.registerType(ROP.pkgPath, ROP.pkgName, ROP.goType)
if err != nil {
ROP.handlerError = err
return
}

if isComplex(ROP.jsonType) && !isBasicGoType(typeName) {
ROP.RO.Content[ContentTypeJson] = newMediaTypeObjectCustomRef(addSchemaRefLinkPrefix(typeName))
return
}

mt.next.execute(ROP)

}

func (mt *RefType) setNext(ROH ResponseObjectHandler) {
mt.next = ROH
}

func getResponseObject(ROP ResponseObjectPackage) (*ResponseObject, error) {
responseObject := &ResponseObject{
Content: map[string]*MediaTypeObject{},
}
responseObject.Description = strings.Trim(ROP.desc, "\"")

ROP.RO = responseObject

//linked list of ResponseObjects handlers

emptyResponseObject := &EmptyType{}
terminalResponseObject := &TerminalMediaType{}
ComplexGoResponseObject := &ComplexGoType{}
SimpleGoResponseObject := &SimpleGoType{}
RefGoResponseObject := &RefType{}
fail := &FailType{}

emptyResponseObject.setNext(terminalResponseObject)
terminalResponseObject.setNext(ComplexGoResponseObject)
ComplexGoResponseObject.setNext(SimpleGoResponseObject)
SimpleGoResponseObject.setNext(RefGoResponseObject)
RefGoResponseObject.setNext(fail)

emptyResponseObject.execute(&ROP)

/*
if isTerminal(ROP.jsonType) {
responseObject.Content[ContentTypeJson] = newMediaTypeObjectCustomType(cleanBrackets(ROP.jsonType))
} else if isComplex(ROP.jsonType) {
if isComplexGoType(ROP.goType) {
schema, err := ROP.p.parseSchemaObject(ROP.pkgPath, ROP.pkgName, ROP.goType)
if err != nil {
ROP.p.debug("parseResponseComment: cannot parse goType", ROP.goType)
}
responseObject.Content[ContentTypeJson] = newMediaTypeObjectCustomSchema(*schema)
} else {
typeName, err := ROP.p.registerType(ROP.pkgPath, ROP.pkgName, ROP.goType)
if err != nil {
return nil, err
}
if isBasicGoType(typeName) {
responseObject.Content[ContentTypeText] = newMediaTypeObjectCustomType("string")
} else {
responseObject.Content[ContentTypeJson] = newMediaTypeObjectCustomRef(addSchemaRefLinkPrefix(typeName))
}
}
} else if ROP.jsonType != "" {
return nil, fmt.Errorf("getResponseObject - unknown json type %v", ROP.jsonType)
}

*/
return ROP.RO, ROP.handlerError
}

type HeaderObject struct {
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
Expand Down
66 changes: 23 additions & 43 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,8 @@ func (p *parser) parseOperation(pkgPath, pkgName string, astComments []*ast.Comm
for _, astComment := range astComments {
comment := strings.TrimSpace(strings.TrimLeft(astComment.Text, "/"))
if len(comment) == 0 {
return nil
// ignore empty lines
continue
}
attribute := strings.Fields(comment)[0]
switch strings.ToLower(attribute) {
Expand Down Expand Up @@ -800,56 +801,35 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation
func (p *parser) parseResponseComment(pkgPath, pkgName string, operation *OperationObject, comment string) error {
// {status} {jsonType} {goType} {description}
// 201 object models.User "User Model"
re := regexp.MustCompile(`([\d]+)[\s]+([\w\{\}]+)[\s]+([\w\-\.\/\[\]]+)[^"]*(.*)?`)
// if 204 or something else without empty return payload
// 204 "User Model"
re := regexp.MustCompile(`(?P<status>[\d]+)[\s]*(?P<jsonType>[\w\{\}]+)?[\s]+(?P<goType>[\w\-\.\/\[\]]+)?[^"]*(?P<description>.*)?`)
matches := re.FindStringSubmatch(comment)
if len(matches) != 5 {
return fmt.Errorf("parseResponseComment can not parse response comment \"%s\"", comment)

paramsMap := make(map[string]string)
for i, name := range re.SubexpNames() {
if i > 0 && i <= len(matches) {
paramsMap[name] = matches[i]
}
}

status := matches[1]
_, err := strconv.Atoi(matches[1])
if len(matches) <= 2 {
return fmt.Errorf("parseResponseComment can not parse response comment \"%s\", matches: %v", comment, matches)
}

status := paramsMap["status"]
_, err := strconv.Atoi(status)
if err != nil {
return fmt.Errorf("parseResponseComment: http status must be int, but got %s", status)
}
switch matches[2] {
case "object", "array", "{object}", "{array}":
default:
return fmt.Errorf("parseResponseComment: invalid jsonType %s", matches[2])
}
responseObject := &ResponseObject{
Content: map[string]*MediaTypeObject{},
}
responseObject.Description = strings.Trim(matches[4], "\"")

re = regexp.MustCompile(`\[\w*\]`)
goType := re.ReplaceAllString(matches[3], "[]")
if strings.HasPrefix(goType, "[]") || strings.HasPrefix(goType, "map[]") {
schema, err := p.parseSchemaObject(pkgPath, pkgName, goType)
if err != nil {
p.debug("parseResponseComment cannot parse goType", goType)
}
responseObject.Content[ContentTypeJson] = &MediaTypeObject{
Schema: *schema,
}
} else {
typeName, err := p.registerType(pkgPath, pkgName, matches[3])
if err != nil {
return err
}
if isBasicGoType(typeName) {
responseObject.Content[ContentTypeText] = &MediaTypeObject{
Schema: SchemaObject{
Type: "string",
},
}
} else {
responseObject.Content[ContentTypeJson] = &MediaTypeObject{
Schema: SchemaObject{
Ref: addSchemaRefLinkPrefix(typeName),
},
}
}
RespObjPkg := ResponseObjectPackage{pkgPath, pkgName, paramsMap["jsonType"], paramsMap["goType"], paramsMap["description"], p, nil, nil}

responseObject, err := getResponseObject(RespObjPkg)
if err != nil {
return err
}

operation.Responses[status] = responseObject

return nil
Expand Down
35 changes: 35 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,37 @@ func isInStringList(list []string, s string) bool {
return false
}

func cleanBrackets(s string) string {
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") {
return s[1 : len(s)-1]
}
return s
}

func isTerminal(s string) bool {
cleanString := cleanBrackets(s)
_, ok := TerminalTypes[cleanString]
return ok
}

func isComplex(s string) bool {
cleanString := cleanBrackets(s)
_, ok := ComplexTypes[cleanString]
return ok
}

var TerminalTypes = map[string]bool{
"string": true,
"number": true,
"integer": true,
"boolean": true,
}

var ComplexTypes = map[string]bool{
"array": true,
"object": true,
}

var basicGoTypes = map[string]bool{
"bool": true,
"uint": true,
Expand Down Expand Up @@ -97,6 +128,10 @@ func isBasicGoType(typeName string) bool {
return ok
}

func isComplexGoType(typeName string) bool {
return strings.HasPrefix(typeName, "[]") || strings.HasPrefix(typeName, "map[]")
}

var goTypesOASTypes = map[string]string{
"bool": "boolean",
"uint": "integer",
Expand Down