Skip to content
This repository has been archived by the owner on Mar 21, 2023. It is now read-only.

Commit

Permalink
support third party library types (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
Raymond Ho authored Mar 24, 2021
1 parent a9ceb17 commit 21dc6e7
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 18 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
- uses: actions/checkout@v2
with:
go-version: 1.15
- uses: actions/checkout@v2
- name: Install example dependencies
working-directory: ./example
run: go install
- run: go test ./... -v
5 changes: 5 additions & 0 deletions example/foo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"encoding/json"
"gopkg.in/mgo.v2/bson"
"time"
)

Expand All @@ -10,8 +11,11 @@ type JsonMap map[string]interface{}
type DoubleAlias JsonMap

type InterfaceResponse interface{}
type Instruction bson.M
type BsonID bson.ObjectId

type FooResponse struct {
BsonID BsonID `json:"bsonId"`
ID string `json:"id"`
StartDate time.Time `json:"startDate"`
EndDate UnixMillis `json:"endDate"`
Expand All @@ -23,6 +27,7 @@ type FooResponse struct {
JsonMap JsonMap `json:"jsonMap"`
DoubleAlias DoubleAlias `json:"doubleAlias"`
InterfaceBlah InterfaceResponse `json:"interfaceBlah"`
Instruction Instruction `json:"instruction"`
}

type Environment struct {
Expand Down
6 changes: 6 additions & 0 deletions example/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
module example.com/example

go 1.15

require (
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
gopkg.in/yaml.v2 v2.4.0 // indirect
)
12 changes: 12 additions & 0 deletions example/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
60 changes: 44 additions & 16 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func newParser(modulePath, mainFilePath, handlerPath string, debug bool) (*parse
goModCacheInfo, err := os.Stat(goModCachePath)
if err != nil {
if os.IsNotExist(err) {
return nil, err
return nil, fmt.Errorf("could not find goModCachePath: %w", err)
}
return nil, fmt.Errorf("cannot get information of %s: %s", goModCachePath, err)
}
Expand Down Expand Up @@ -835,7 +835,7 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation
}
if goType == "time.Time" {
var err error
parameterObject.Schema, err = p.parseSchemaObject(pkgPath, pkgName, goType)
parameterObject.Schema, err = p.parseSchemaObject(pkgPath, pkgName, goType, true)
if err != nil {
p.debug("parseResponseComment cannot parse goType", goType)
}
Expand All @@ -859,7 +859,7 @@ func (p *parser) parseParamComment(pkgPath, pkgName string, operation *Operation
}

if strings.HasPrefix(goType, "[]") || strings.HasPrefix(goType, "map[]") || goType == "time.Time" {
schema, err := p.parseSchemaObject(pkgPath, pkgName, goType)
schema, err := p.parseSchemaObject(pkgPath, pkgName, goType, true)
if err != nil {
p.debug("parseResponseComment cannot parse goType", goType)
}
Expand Down Expand Up @@ -932,7 +932,7 @@ func (p *parser) parseResponseComment(pkgPath, pkgName string, operation *Operat
re = regexp.MustCompile(`\[\w*\]`)
goType := re.ReplaceAllString(goTypeRaw, "[]")
if strings.HasPrefix(goType, "[]") || strings.HasPrefix(goType, "map[]") {
schema, err := p.parseSchemaObject(pkgPath, pkgName, goType)
schema, err := p.parseSchemaObject(pkgPath, pkgName, goType, true)
if err != nil {
p.debug("parseResponseComment: cannot parse goType", goType)
}
Expand Down Expand Up @@ -1009,7 +1009,7 @@ func (p *parser) getSchemaObjectCached(pkgPath, pkgName, typeName string) (*Sche
schemaObject = knownObj
} else {
// if not, parse it now
parsedObject, err := p.parseSchemaObject(pkgPath, pkgName, typeName)
parsedObject, err := p.parseSchemaObject(pkgPath, pkgName, typeName, true)
if err != nil {
return schemaObject, err
}
Expand All @@ -1034,7 +1034,7 @@ func (p *parser) registerType(pkgPath, pkgName, typeName string) (string, error)
return registerTypeName, nil
}

func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaObject, error) {
func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string, register bool) (*SchemaObject, error) {
var typeSpec *ast.TypeSpec
var exist bool
var schemaObject SchemaObject
Expand All @@ -1049,7 +1049,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb
schemaObject.Items = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)}
return &schemaObject, nil
}
schemaObject.Items, err = p.parseSchemaObject(pkgPath, pkgName, itemTypeName)
schemaObject.Items, err = p.parseSchemaObject(pkgPath, pkgName, itemTypeName, true)
if err != nil {
return nil, err
}
Expand All @@ -1062,7 +1062,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb
schemaObject.AdditionalProperties = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)}
return &schemaObject, nil
}
schemaProperty, err := p.parseSchemaObject(pkgPath, pkgName, itemTypeName)
schemaProperty, err := p.parseSchemaObject(pkgPath, pkgName, itemTypeName, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1156,7 +1156,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb
schemaObject.Type = goTypesOASTypes[p.getTypeAsString(typeSpec.Type)]
} else if astIdent, ok := typeSpec.Type.(*ast.Ident); ok {
// this is for type aliases to custom types
newSchema, err := p.parseSchemaObject(pkgPath, pkgName, astIdent.Name)
newSchema, err := p.parseSchemaObject(pkgPath, pkgName, astIdent.Name, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1208,18 +1208,46 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb
} else if isGoTypeOASType(typeAsString) {
propertySchema.Type = goTypesOASTypes[typeAsString]
}
} else if selectorType, ok := typeSpec.Type.(*ast.SelectorExpr); ok {
// this case is for referencing third party packages.
packageIdentifier, ok := selectorType.X.(*ast.Ident)
usedTypeName := selectorType.Sel.Name
if ok {
packageName := packageIdentifier.Name
for potentialPackage, typeSpecs := range p.TypeSpecs {
if strings.HasSuffix(potentialPackage, packageName) {
// iterate through types of that package
for name, _ := range typeSpecs {
if name == usedTypeName {
parsedPackageSchema, err := p.parseSchemaObject(potentialPackage, potentialPackage, usedTypeName, false)
if err != nil {
return nil, err
}
schemaObject.Type = parsedPackageSchema.Type
schemaObject.Properties = parsedPackageSchema.Properties
schemaObject.AdditionalProperties = parsedPackageSchema.AdditionalProperties
break
}
}
}
}

}
} else if _, ok := typeSpec.Type.(*ast.InterfaceType); ok {
// type points to an interface, the most we can do is give it an object type..
schemaObject.Type = "object"
// free form object since the interface can be "anything"
schemaObject.AdditionalProperties = &SchemaObject{}
}

// register schema object in spec tree if it doesn't exist
registerTypeName := schemaObject.ID
_, ok := p.OpenAPI.Components.Schemas[replaceBackslash(registerTypeName)]
if !ok {
p.OpenAPI.Components.Schemas[replaceBackslash(registerTypeName)] = &schemaObject
// we don't want to register 3rd party library types
if register {
// register schema object in spec tree if it doesn't exist
registerTypeName := schemaObject.ID
_, ok := p.OpenAPI.Components.Schemas[replaceBackslash(registerTypeName)]
if !ok {
p.OpenAPI.Components.Schemas[replaceBackslash(registerTypeName)] = &schemaObject
}
}

return &schemaObject, nil
Expand Down Expand Up @@ -1254,7 +1282,7 @@ func (p *parser) parseSchemaPropertiesFromStructFields(pkgPath, pkgName string,
isInterface := strings.HasPrefix(typeAsString, "interface{}")
if isSliceOrMap || isInterface || typeAsString == "time.Time" {
var err error
fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString)
fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString, true)
if err != nil {
p.debug(err)
return
Expand All @@ -1269,7 +1297,7 @@ func (p *parser) parseSchemaPropertiesFromStructFields(pkgPath, pkgName string,
if ok {
fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID)
} else {
fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString)
fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString, true)
if err != nil {
p.debug(err)
return
Expand Down
17 changes: 16 additions & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ func TestExample(t *testing.T) {
},
"components": {
"schemas": {
"BsonID": {
"type": "string"
},
"DoubleAlias": {
"type": "object",
"additionalProperties": {
Expand All @@ -142,6 +145,9 @@ func TestExample(t *testing.T) {
"FooResponse": {
"type": "object",
"properties": {
"bsonId": {
"$ref": "#/components/schemas/BsonID"
},
"id": {
"type": "string"
},
Expand Down Expand Up @@ -194,6 +200,9 @@ func TestExample(t *testing.T) {
},
"interfaceBlah": {
"$ref": "#/components/schemas/InterfaceResponse"
},
"instruction": {
"$ref": "#/components/schemas/Instruction"
}
}
},
Expand All @@ -208,6 +217,12 @@ func TestExample(t *testing.T) {
}
}
},
"Instruction": {
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"InterfaceResponse": {
"type": "object",
"additionalProperties": {}
Expand Down Expand Up @@ -245,7 +260,7 @@ func TestExample(t *testing.T) {

func TestDeterministic(t *testing.T) {
var allOutputs []string
for i := 0; i < 100; i++ {
for i := 0; i < 10; i++ {
p, err := newParser("example/", "example/main.go", "", false)
require.NoError(t, err)

Expand Down

0 comments on commit 21dc6e7

Please sign in to comment.