From 00f5850c5c540af27688f988d77dadf70f35100a Mon Sep 17 00:00:00 2001 From: Alvaro Frias Garay Date: Mon, 4 Apr 2022 17:16:34 -0300 Subject: [PATCH 1/5] Applied patch for parser problems content parsing for response is now optional --- parser.go | 88 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/parser.go b/parser.go index 402eb57..0ded7fa 100644 --- a/parser.go +++ b/parser.go @@ -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) { @@ -800,53 +801,70 @@ 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[\d]+)[\s]*(?P[\w\{\}]+)?[\s]+(?P[\w\-\.\/\[\]]+)?[^"]*(?P.*)?`) 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] + } + } + + if len(matches) <= 2 { + return fmt.Errorf("parseResponseComment can not parse response comment \"%s\", matches: %v", comment, matches) } - status := matches[1] - _, err := strconv.Atoi(matches[1]) + 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]) + + // ignore type if not set + if jsonType := paramsMap["jsonType"]; jsonType != "" { + switch jsonType { + case "object", "array", "{object}", "{array}": + default: + return fmt.Errorf("parseResponseComment: invalid jsonType \"%s\"", paramsMap["jsonType"]) + } } + responseObject := &ResponseObject{ Content: map[string]*MediaTypeObject{}, } - responseObject.Description = strings.Trim(matches[4], "\"") + responseObject.Description = strings.Trim(paramsMap["description"], "\"") - 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", - }, + if goTypeRaw := paramsMap["goType"]; goTypeRaw != "" { + re = regexp.MustCompile(`\[\w*\]`) + goType := re.ReplaceAllString(goTypeRaw, "[]") + 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) } - } else { responseObject.Content[ContentTypeJson] = &MediaTypeObject{ - Schema: SchemaObject{ - Ref: addSchemaRefLinkPrefix(typeName), - }, + 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), + }, + } } } } From fce2da680c2f1b7e21200151b35abf639082ff96 Mon Sep 17 00:00:00 2001 From: Alvaro Frias Garay Date: Fri, 8 Apr 2022 17:13:13 -0300 Subject: [PATCH 2/5] Refactor Response Object creation Added parsing for simple json types --- oas.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 1 deletion(-) diff --git a/oas.go b/oas.go index 94e1d34..ca20f67 100644 --- a/oas.go +++ b/oas.go @@ -1,6 +1,11 @@ package main -import "github.com/iancoleman/orderedmap" +import ( + "fmt" + "strings" + + "github.com/iancoleman/orderedmap" +) const ( OpenAPIVersion = "3.0.0" @@ -191,6 +196,181 @@ 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) + + return ROP.RO, ROP.handlerError +} + type HeaderObject struct { Description string `json:"description,omitempty"` Type string `json:"type,omitempty"` From 32e576ca2ed75b83358ce9935629b05ebfcd7b91 Mon Sep 17 00:00:00 2001 From: Alvaro Frias Garay Date: Fri, 8 Apr 2022 17:14:35 -0300 Subject: [PATCH 3/5] Updated parseResponseComment with refactores getResponseObject --- parser.go | 46 ++++------------------------------------------ 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/parser.go b/parser.go index 0ded7fa..93b7ac6 100644 --- a/parser.go +++ b/parser.go @@ -823,51 +823,13 @@ func (p *parser) parseResponseComment(pkgPath, pkgName string, operation *Operat return fmt.Errorf("parseResponseComment: http status must be int, but got %s", status) } - // ignore type if not set - if jsonType := paramsMap["jsonType"]; jsonType != "" { - switch jsonType { - case "object", "array", "{object}", "{array}": - default: - return fmt.Errorf("parseResponseComment: invalid jsonType \"%s\"", paramsMap["jsonType"]) - } - } + RespObjPkg := ResponseObjectPackage{pkgPath, pkgName, paramsMap["jsonType"], paramsMap["goType"], paramsMap["description"], p, nil, nil} - responseObject := &ResponseObject{ - Content: map[string]*MediaTypeObject{}, + responseObject, err := getResponseObject(RespObjPkg) + if err != nil { + return err } - responseObject.Description = strings.Trim(paramsMap["description"], "\"") - if goTypeRaw := paramsMap["goType"]; goTypeRaw != "" { - re = regexp.MustCompile(`\[\w*\]`) - goType := re.ReplaceAllString(goTypeRaw, "[]") - 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), - }, - } - } - } - } operation.Responses[status] = responseObject return nil From 3e1c27383ab099ca33754cd00efaaf9604095cb8 Mon Sep 17 00:00:00 2001 From: Alvaro Frias Garay Date: Fri, 8 Apr 2022 17:15:13 -0300 Subject: [PATCH 4/5] Add helper structs --- util.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/util.go b/util.go index e531910..3ad3f9e 100644 --- a/util.go +++ b/util.go @@ -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, @@ -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", From e55e3f84920ecfccb1e5a8ce165240ba77a8f404 Mon Sep 17 00:00:00 2001 From: Alvaro Frias Garay Date: Mon, 25 Apr 2022 18:09:16 -0300 Subject: [PATCH 5/5] Clarifying comments --- oas.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/oas.go b/oas.go index ca20f67..95def55 100644 --- a/oas.go +++ b/oas.go @@ -368,6 +368,32 @@ func getResponseObject(ROP ResponseObjectPackage) (*ResponseObject, error) { 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 }