From 744a313073fb868ac67464a5225510da90e1391c Mon Sep 17 00:00:00 2001 From: Sheena Carswell Date: Wed, 10 Mar 2021 15:38:03 -0800 Subject: [PATCH 01/13] [ch101847] Failing test for UnixMillis --- example/foo.go | 4 ++++ parser_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/example/foo.go b/example/foo.go index bd7dc62..50b7c8f 100644 --- a/example/foo.go +++ b/example/foo.go @@ -5,9 +5,13 @@ import ( "time" ) +type UnixMillis int64 + type FooResponse struct { ID string `json:"id"` StartDate time.Time `json:"startDate"` + EndDate UnixMillis `json:"endDate"` + Count int64 `json:"count"` Msg json.RawMessage `json:"msg"` InnerFoos []InnerFoo `json:"foo"` Environments map[string]Environment `json:"environments"` diff --git a/parser_test.go b/parser_test.go index 3b65eba..45bf3c0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -31,6 +31,12 @@ func TestExample(t *testing.T) { }, "FooResponse": { "properties": { + "count": { + "type": "integer" + }, + "endDate": { + "type": "integer" + }, "environments": { "additionalProperties": { "properties": { From 18516d6eb68a5ef7805b4dac4283359978f43b2d Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Wed, 10 Mar 2021 16:03:08 -0800 Subject: [PATCH 02/13] support type aliases of basic go types --- parser.go | 39 +++++++++++++++++++++------------------ util.go | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/parser.go b/parser.go index 6042bbf..28ba4b1 100644 --- a/parser.go +++ b/parser.go @@ -1016,7 +1016,7 @@ func (p *parser) registerType(pkgPath, pkgName, typeName string) (string, error) var schemaObject *SchemaObject // see if we've already parsed this type - if knownObj, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, typeName)]; ok { + if knownObj, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, typeName)]; ok { schemaObject = knownObj } else { // if not, parse it now @@ -1041,7 +1041,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if strings.HasPrefix(typeName, "[]") { schemaObject.Type = "array" itemTypeName := typeName[2:] - schema, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, itemTypeName)] + schema, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, itemTypeName)] if ok { schemaObject.Items = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} return &schemaObject, nil @@ -1054,7 +1054,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb } else if strings.HasPrefix(typeName, "map[]") { schemaObject.Type = "object" itemTypeName := typeName[5:] - schema, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, itemTypeName)] + schema, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, itemTypeName)] if ok { schemaObject.AdditionalProperties = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} return &schemaObject, nil @@ -1094,7 +1094,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb } } schemaObject.PkgName = pkgName - schemaObject.ID = genSchemeaObjectID(pkgName, typeName) + schemaObject.ID = genSchemaObjectID(pkgName, typeName) p.KnownIDSchema[schemaObject.ID] = &schemaObject } else { guessPkgName := strings.Join(typeNameParts[:len(typeNameParts)-1], "/") @@ -1128,6 +1128,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb } } // p.debugf("guess %s ast.TypeSpec in package %s", guessTypeName, guessPkgName) + typeSpec, exist = p.getTypeSpec(guessPkgPath, guessPkgName, guessTypeName) if !exist { if p.CorePkgs[guessPkgName] == true { @@ -1141,13 +1142,15 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb guessTypeName, guessPkgName) } schemaObject.PkgName = guessPkgName - schemaObject.ID = genSchemeaObjectID(guessPkgName, guessTypeName) + schemaObject.ID = genSchemaObjectID(guessPkgName, guessTypeName) p.KnownIDSchema[schemaObject.ID] = &schemaObject } pkgPath, pkgName = guessPkgPath, guessPkgName } - if astIdent, ok := typeSpec.Type.(*ast.Ident); ok { + if isGoTypeOASType(p.getTypeAsString(typeSpec.Type)) { + schemaObject.Type = goTypesOASTypes[p.getTypeAsString(typeSpec.Type)] + } else if astIdent, ok := typeSpec.Type.(*ast.Ident); ok { _ = astIdent } else if astStructType, ok := typeSpec.Type.(*ast.StructType); ok { schemaObject.Type = "object" @@ -1160,11 +1163,11 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb typeAsString := p.getTypeAsString(astArrayType.Elt) typeAsString = strings.TrimLeft(typeAsString, "*") if !isBasicGoType(typeAsString) { - schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + schemaItemsSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - schemaObject.Items.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) + schemaObject.Items.Ref = addSchemaRefLinkPrefix(schemaItemsSchemaObjectID) } } else if isGoTypeOASType(typeAsString) { schemaObject.Items.Type = goTypesOASTypes[typeAsString] @@ -1177,11 +1180,11 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb typeAsString := p.getTypeAsString(astMapType.Value) typeAsString = strings.TrimLeft(typeAsString, "*") if !isBasicGoType(typeAsString) { - schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + schemaItemsSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - propertySchema.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) + propertySchema.Ref = addSchemaRefLinkPrefix(schemaItemsSchemaObjectID) } } else if isGoTypeOASType(typeAsString) { propertySchema.Type = goTypesOASTypes[typeAsString] @@ -1252,18 +1255,18 @@ astFieldsLoop: return } } else if !isBasicGoType(typeAsString) { - fieldSchemaSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + fieldSchemaSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaPropertiesFromStructFields err:", err) } else { - fieldSchema.ID = fieldSchemaSchemeaObjectID - schema, ok := p.KnownIDSchema[fieldSchemaSchemeaObjectID] + fieldSchema.ID = fieldSchemaSchemaObjectID + schema, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] if ok { fieldSchema.Type = schema.Type if schema.Items != nil { fieldSchema.Items = schema.Items } - fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID) + fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemaObjectID) } else { fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) if err != nil { @@ -1402,19 +1405,19 @@ astFieldsLoop: return } } else if !isBasicGoType(typeAsString) { - fieldSchemaSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + fieldSchemaSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaPropertiesFromStructFields err:", err) } else { - fieldSchema.ID = fieldSchemaSchemeaObjectID - schema, ok := p.KnownIDSchema[fieldSchemaSchemeaObjectID] + fieldSchema.ID = fieldSchemaSchemaObjectID + schema, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] if ok { fieldSchema.Type = schema.Type if schema.Items != nil { fieldSchema.Items = schema.Items } } - fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID) + fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemaObjectID) } } else if isGoTypeOASType(typeAsString) { fieldSchema.Type = goTypesOASTypes[typeAsString] diff --git a/util.go b/util.go index 808e7c9..f283d11 100644 --- a/util.go +++ b/util.go @@ -151,7 +151,7 @@ func trimeSchemaRefLinkPrefix(ref string) string { return strings.TrimPrefix(ref, "#/components/schemas/") } -func genSchemeaObjectID(pkgName, typeName string) string { +func genSchemaObjectID(pkgName, typeName string) string { typeNameParts := strings.Split(typeName, ".") return typeNameParts[len(typeNameParts)-1] } From 2eca0a67bf8ffa1c95bdbb5e597182aab03888e3 Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Wed, 10 Mar 2021 16:29:13 -0800 Subject: [PATCH 03/13] don't emit type when we have a ref --- parser.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/parser.go b/parser.go index 28ba4b1..e7b05ea 100644 --- a/parser.go +++ b/parser.go @@ -1148,7 +1148,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb pkgPath, pkgName = guessPkgPath, guessPkgName } - if isGoTypeOASType(p.getTypeAsString(typeSpec.Type)) { + if isGoTypeOASType(p.getTypeAsString(typeSpec.Type)) && schemaObject.Ref == "" { schemaObject.Type = goTypesOASTypes[p.getTypeAsString(typeSpec.Type)] } else if astIdent, ok := typeSpec.Type.(*ast.Ident); ok { _ = astIdent @@ -1260,12 +1260,8 @@ astFieldsLoop: p.debug("parseSchemaPropertiesFromStructFields err:", err) } else { fieldSchema.ID = fieldSchemaSchemaObjectID - schema, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] + _, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] if ok { - fieldSchema.Type = schema.Type - if schema.Items != nil { - fieldSchema.Items = schema.Items - } fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemaObjectID) } else { fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) From d3d02b2a270dd324d8408e3ea30e3f55581c32be Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Wed, 10 Mar 2021 16:33:20 -0800 Subject: [PATCH 04/13] restore typo to make diff better --- parser.go | 34 +++++++++++++++++----------------- util.go | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/parser.go b/parser.go index e7b05ea..1a774a2 100644 --- a/parser.go +++ b/parser.go @@ -1016,7 +1016,7 @@ func (p *parser) registerType(pkgPath, pkgName, typeName string) (string, error) var schemaObject *SchemaObject // see if we've already parsed this type - if knownObj, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, typeName)]; ok { + if knownObj, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, typeName)]; ok { schemaObject = knownObj } else { // if not, parse it now @@ -1041,7 +1041,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if strings.HasPrefix(typeName, "[]") { schemaObject.Type = "array" itemTypeName := typeName[2:] - schema, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, itemTypeName)] + schema, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, itemTypeName)] if ok { schemaObject.Items = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} return &schemaObject, nil @@ -1054,7 +1054,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb } else if strings.HasPrefix(typeName, "map[]") { schemaObject.Type = "object" itemTypeName := typeName[5:] - schema, ok := p.KnownIDSchema[genSchemaObjectID(pkgName, itemTypeName)] + schema, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, itemTypeName)] if ok { schemaObject.AdditionalProperties = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} return &schemaObject, nil @@ -1094,7 +1094,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb } } schemaObject.PkgName = pkgName - schemaObject.ID = genSchemaObjectID(pkgName, typeName) + schemaObject.ID = genSchemeaObjectID(pkgName, typeName) p.KnownIDSchema[schemaObject.ID] = &schemaObject } else { guessPkgName := strings.Join(typeNameParts[:len(typeNameParts)-1], "/") @@ -1142,7 +1142,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb guessTypeName, guessPkgName) } schemaObject.PkgName = guessPkgName - schemaObject.ID = genSchemaObjectID(guessPkgName, guessTypeName) + schemaObject.ID = genSchemeaObjectID(guessPkgName, guessTypeName) p.KnownIDSchema[schemaObject.ID] = &schemaObject } pkgPath, pkgName = guessPkgPath, guessPkgName @@ -1163,11 +1163,11 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb typeAsString := p.getTypeAsString(astArrayType.Elt) typeAsString = strings.TrimLeft(typeAsString, "*") if !isBasicGoType(typeAsString) { - schemaItemsSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - schemaObject.Items.Ref = addSchemaRefLinkPrefix(schemaItemsSchemaObjectID) + schemaObject.Items.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) } } else if isGoTypeOASType(typeAsString) { schemaObject.Items.Type = goTypesOASTypes[typeAsString] @@ -1180,11 +1180,11 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb typeAsString := p.getTypeAsString(astMapType.Value) typeAsString = strings.TrimLeft(typeAsString, "*") if !isBasicGoType(typeAsString) { - schemaItemsSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - propertySchema.Ref = addSchemaRefLinkPrefix(schemaItemsSchemaObjectID) + propertySchema.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) } } else if isGoTypeOASType(typeAsString) { propertySchema.Type = goTypesOASTypes[typeAsString] @@ -1255,14 +1255,14 @@ astFieldsLoop: return } } else if !isBasicGoType(typeAsString) { - fieldSchemaSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + fieldSchemaSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaPropertiesFromStructFields err:", err) } else { - fieldSchema.ID = fieldSchemaSchemaObjectID - _, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] + fieldSchema.ID = fieldSchemaSchemeaObjectID + _, ok := p.KnownIDSchema[fieldSchemaSchemeaObjectID] if ok { - fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemaObjectID) + fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID) } else { fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) if err != nil { @@ -1401,19 +1401,19 @@ astFieldsLoop: return } } else if !isBasicGoType(typeAsString) { - fieldSchemaSchemaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + fieldSchemaSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaPropertiesFromStructFields err:", err) } else { - fieldSchema.ID = fieldSchemaSchemaObjectID - schema, ok := p.KnownIDSchema[fieldSchemaSchemaObjectID] + fieldSchema.ID = fieldSchemaSchemeaObjectID + schema, ok := p.KnownIDSchema[fieldSchemaSchemeaObjectID] if ok { fieldSchema.Type = schema.Type if schema.Items != nil { fieldSchema.Items = schema.Items } } - fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemaObjectID) + fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID) } } else if isGoTypeOASType(typeAsString) { fieldSchema.Type = goTypesOASTypes[typeAsString] diff --git a/util.go b/util.go index f283d11..808e7c9 100644 --- a/util.go +++ b/util.go @@ -151,7 +151,7 @@ func trimeSchemaRefLinkPrefix(ref string) string { return strings.TrimPrefix(ref, "#/components/schemas/") } -func genSchemaObjectID(pkgName, typeName string) string { +func genSchemeaObjectID(pkgName, typeName string) string { typeNameParts := strings.Split(typeName, ".") return typeNameParts[len(typeNameParts)-1] } From a91cc4489c36537223b117805a3185aa332ff04e Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Wed, 10 Mar 2021 16:49:16 -0800 Subject: [PATCH 05/13] fix test --- parser_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/parser_test.go b/parser_test.go index 45bf3c0..5442ac9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -31,11 +31,11 @@ func TestExample(t *testing.T) { }, "FooResponse": { "properties": { - "count": { + "count": { "type": "integer" }, "endDate": { - "type": "integer" + "$ref": "#/components/schemas/UnixMillis" }, "environments": { "additionalProperties": { @@ -85,7 +85,10 @@ func TestExample(t *testing.T) { } }, "type": "object" - } + }, + "UnixMillis": { + "type": "integer" + } }, "securitySchemes": { "ApiKey": { From 92bf75ec2b6fa37409b947ca5b8107a36e2563ac Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Mon, 22 Mar 2021 10:15:35 -0700 Subject: [PATCH 06/13] fix some type alias issues --- parser.go | 55 +++++++++++++++++++++++++++++++++----------------- parser_test.go | 12 +++++++++++ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/parser.go b/parser.go index 1a774a2..8b6f374 100644 --- a/parser.go +++ b/parser.go @@ -1006,25 +1006,33 @@ func (p *parser) parseRouteComment(operation *OperationObject, comment string) e return nil } +func (p *parser) getSchemaObjectCached(pkgPath, pkgName, typeName string) (*SchemaObject, error) { + var schemaObject *SchemaObject + + // see if we've already parsed this type + if knownObj, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, typeName)]; ok { + schemaObject = knownObj + } else { + // if not, parse it now + parsedObject, err := p.parseSchemaObject(pkgPath, pkgName, typeName) + if err != nil { + return schemaObject, err + } + schemaObject = parsedObject + } + + return schemaObject, nil +} + func (p *parser) registerType(pkgPath, pkgName, typeName string) (string, error) { var registerTypeName string if isBasicGoType(typeName) { registerTypeName = typeName } else { - - var schemaObject *SchemaObject - - // see if we've already parsed this type - if knownObj, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, typeName)]; ok { - schemaObject = knownObj - } else { - // if not, parse it now - parsedObject, err := p.parseSchemaObject(pkgPath, pkgName, typeName) - if err != nil { - return "", err - } - schemaObject = parsedObject + schemaObject, err := p.getSchemaObjectCached(pkgPath, pkgName, typeName) + if err != nil { + return "", err } registerTypeName = schemaObject.ID } @@ -1070,6 +1078,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb schemaObject.Format = "date-time" return &schemaObject, nil } else if strings.HasPrefix(typeName, "interface{}") { + schemaObject.Type = "object" return &schemaObject, nil } else if isGoTypeOASType(typeName) { schemaObject.Type = goTypesOASTypes[typeName] @@ -1162,29 +1171,37 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb schemaObject.Items = &SchemaObject{} typeAsString := p.getTypeAsString(astArrayType.Elt) typeAsString = strings.TrimLeft(typeAsString, "*") + if !isBasicGoType(typeAsString) { - schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + itemsSchema, err := p.getSchemaObjectCached(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - schemaObject.Items.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) + if itemsSchema.ID != "" { + schemaObject.Items.Ref = addSchemaRefLinkPrefix(itemsSchema.ID) + } else { + *schemaObject.Items = *itemsSchema + } } } else if isGoTypeOASType(typeAsString) { schemaObject.Items.Type = goTypesOASTypes[typeAsString] } } else if astMapType, ok := typeSpec.Type.(*ast.MapType); ok { schemaObject.Type = "object" - schemaObject.Properties = orderedmap.New() propertySchema := &SchemaObject{} - schemaObject.Properties.Set("key", propertySchema) + schemaObject.AdditionalProperties = propertySchema typeAsString := p.getTypeAsString(astMapType.Value) typeAsString = strings.TrimLeft(typeAsString, "*") if !isBasicGoType(typeAsString) { - schemaItemsSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) + keySchema, err := p.getSchemaObjectCached(pkgPath, pkgName, typeAsString) if err != nil { p.debug("parseSchemaObject parse array items err:", err) } else { - propertySchema.Ref = addSchemaRefLinkPrefix(schemaItemsSchemeaObjectID) + if keySchema.ID != "" { + propertySchema.Ref = addSchemaRefLinkPrefix(keySchema.ID) + } else { + *propertySchema = *keySchema + } } } else if isGoTypeOASType(typeAsString) { propertySchema.Type = goTypesOASTypes[typeAsString] diff --git a/parser_test.go b/parser_test.go index 5442ac9..891c28c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -62,9 +62,15 @@ func TestExample(t *testing.T) { }, "type": "array" }, + "freeForm": { + "type": "object" + }, "id": { "type": "string" }, + "jsonMap": { + "$ref": "#/components/schemas/JsonMap" + }, "msg": { "type": "object" }, @@ -86,6 +92,12 @@ func TestExample(t *testing.T) { }, "type": "object" }, + "JsonMap": { + "additionalProperties": { + "type": "object" + }, + "type": "object" + }, "UnixMillis": { "type": "integer" } From 98bf4dc8fa6bff01dc6d10ec7bc09ef5c8102f1d Mon Sep 17 00:00:00 2001 From: Kevin Brackbill Date: Mon, 22 Mar 2021 10:15:50 -0700 Subject: [PATCH 07/13] double alias failing test --- example/foo.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/example/foo.go b/example/foo.go index 50b7c8f..48d68e7 100644 --- a/example/foo.go +++ b/example/foo.go @@ -6,15 +6,20 @@ import ( ) type UnixMillis int64 +type JsonMap map[string]interface{} +type DoubleAlias JsonMap type FooResponse struct { - ID string `json:"id"` - StartDate time.Time `json:"startDate"` - EndDate UnixMillis `json:"endDate"` - Count int64 `json:"count"` - Msg json.RawMessage `json:"msg"` - InnerFoos []InnerFoo `json:"foo"` + ID string `json:"id"` + StartDate time.Time `json:"startDate"` + EndDate UnixMillis `json:"endDate"` + Count int64 `json:"count"` + Msg json.RawMessage `json:"msg"` + InnerFoos []InnerFoo `json:"foo"` Environments map[string]Environment `json:"environments"` + FreeForm interface{} `json:"freeForm"` + JsonMap JsonMap `json:"jsonMap"` + DoubleAlias DoubleAlias `json:"doubleAlias"` } type Environment struct { @@ -42,4 +47,3 @@ func getAllFoos() { func putFoo() { } - From 9528d41c9865c1fb2aa9952aa195067239b942de Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 15:34:21 -0700 Subject: [PATCH 08/13] collapse duplicate parser logic --- parser.go | 27 +----- parser_test.go | 233 +++++++++++++++++++++++++------------------------ util.go | 3 + 3 files changed, 126 insertions(+), 137 deletions(-) diff --git a/parser.go b/parser.go index 8b6f374..14aed55 100644 --- a/parser.go +++ b/parser.go @@ -548,11 +548,6 @@ func (p *parser) parseAPIs() error { return err } - // err = p.parsePaths() - // if err != nil { - // return err - // } - return p.parsePaths() } @@ -1247,25 +1242,9 @@ astFieldsLoop: fieldSchema := &SchemaObject{} typeAsString := p.getTypeAsString(astField.Type) typeAsString = strings.TrimLeft(typeAsString, "*") - if strings.HasPrefix(typeAsString, "[]") { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if strings.HasPrefix(typeAsString, "map[]") { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if typeAsString == "time.Time" { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if strings.HasPrefix(typeAsString, "interface{}") { + isSliceOrMap := strings.HasPrefix(typeAsString, "[]") || strings.HasPrefix(typeAsString, "map[]") + isInterface := strings.HasPrefix(typeAsString, "interface{}") + if isSliceOrMap || isInterface || typeAsString == "time.Time" { fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) if err != nil { p.debug(err) diff --git a/parser_test.go b/parser_test.go index 891c28c..28aae9c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -17,128 +18,43 @@ func TestExample(t *testing.T) { bts, err := json.MarshalIndent(p.OpenAPI, "", " ") require.NoError(t, err) + fmt.Println(string(bts)) + expected := ` { - "components": { - "schemas": { - "Environment": { - "properties": { - "name": { - "type": "string" - } - }, - "type": "object" - }, - "FooResponse": { - "properties": { - "count": { - "type": "integer" - }, - "endDate": { - "$ref": "#/components/schemas/UnixMillis" - }, - "environments": { - "additionalProperties": { - "properties": { - "name": { - "type": "string" - } - }, - "type": "object" - }, - "type": "object" - }, - "foo": { - "items": { - "properties": { - "a": { - "type": "string" - }, - "b": { - "type": "string" - } - }, - "type": "object" - }, - "type": "array" - }, - "freeForm": { - "type": "object" - }, - "id": { - "type": "string" - }, - "jsonMap": { - "$ref": "#/components/schemas/JsonMap" - }, - "msg": { - "type": "object" - }, - "startDate": { - "format": "date-time", - "type": "string" - } - }, - "type": "object" - }, - "InnerFoo": { - "properties": { - "a": { - "type": "string" - }, - "b": { - "type": "string" - } - }, - "type": "object" - }, - "JsonMap": { - "additionalProperties": { - "type": "object" - }, - "type": "object" - }, - "UnixMillis": { - "type": "integer" - } - }, - "securitySchemes": { - "ApiKey": { - "in": "header", - "name": "Authorization", - "type": "apiKey" - } - } - }, + "openapi": "3.0.0", "info": { + "title": "LaunchDarkly REST API", + "description": "Build custom integrations with the LaunchDarkly REST API", "contact": { - "email": "support@launchdarkly.com", "name": "LaunchDarkly Technical Support Team", - "url": "https://support.launchdarkly.com" + "url": "https://support.launchdarkly.com", + "email": "support@launchdarkly.com" }, - "description": "Build custom integrations with the LaunchDarkly REST API", "license": { "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0" }, - "title": "LaunchDarkly REST API", "version": "2.0" }, - "openapi": "3.0.0", + "servers": [ + { + "url": "https://app.launchdarkly.com" + } + ], "paths": { "/api/v2/foo": { "get": { - "description": " Get all foos", "responses": { "200": { + "description": "Successful foo response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FooResponse" } } - }, - "description": "Successful foo response" + } }, "401": { "description": "Invalid access token" @@ -150,20 +66,20 @@ func TestExample(t *testing.T) { "description": "Invalid resource identifier" } }, - "summary": "Get all foos" + "summary": "Get all foos", + "description": " Get all foos" }, "put": { - "description": " Overwrite a foo", "responses": { "200": { + "description": "Successful foo response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FooResponse" } } - }, - "description": "Successful foo response" + } }, "401": { "description": "Invalid access token" @@ -175,22 +91,22 @@ func TestExample(t *testing.T) { "description": "Invalid resource identifier" } }, - "summary": "Put foo" + "summary": "Put foo", + "description": " Overwrite a foo" } }, "/api/v2/foo/{id}/inner": { "put": { - "description": " Get Inner Foos", "responses": { "200": { + "description": "Successful innerfoo response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InnerFoo" } } - }, - "description": "Successful innerfoo response" + } }, "401": { "description": "Invalid access token" @@ -202,7 +118,103 @@ func TestExample(t *testing.T) { "description": "Invalid resource identifier" } }, - "summary": "Get inner foos" + "summary": "Get inner foos", + "description": " Get Inner Foos" + } + } + }, + "components": { + "schemas": { + "DoubleAlias": {}, + "Environment": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "FooResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "startDate": { + "type": "string", + "format": "date-time" + }, + "endDate": { + "$ref": "#/components/schemas/UnixMillis" + }, + "count": { + "type": "integer" + }, + "msg": { + "type": "object" + }, + "foo": { + "type": "array", + "items": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + } + } + }, + "environments": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "freeForm": { + "type": "object" + }, + "jsonMap": { + "$ref": "#/components/schemas/JsonMap" + }, + "doubleAlias": { + "$ref": "#/components/schemas/DoubleAlias" + } + } + }, + "InnerFoo": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + } + }, + "JsonMap": { + "type": "object", + "additionalProperties": { + "type": "object" + } + }, + "UnixMillis": { + "type": "integer" + } + }, + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "in": "header", + "name": "Authorization" } } }, @@ -213,11 +225,6 @@ func TestExample(t *testing.T) { "write" ] } - ], - "servers": [ - { - "url": "https://app.launchdarkly.com" - } ] } ` diff --git a/util.go b/util.go index 808e7c9..b4cc286 100644 --- a/util.go +++ b/util.go @@ -141,6 +141,9 @@ var goTypesOASFormats = map[string]string{ // var modelNamesPackageNames = map[string]string{} func addSchemaRefLinkPrefix(name string) string { + if name == "" { + log.Fatalln("schema does not reference valid name") + } if strings.HasPrefix(name, "#/components/schemas/") { return replaceBackslash(name) } From ceed87fdbb63c6e4c4d45915d7c7fb5b6e865a4b Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 15:47:40 -0700 Subject: [PATCH 09/13] collapse duplicate parser logic part 2 --- parser.go | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/parser.go b/parser.go index 14aed55..0bcdba2 100644 --- a/parser.go +++ b/parser.go @@ -1372,25 +1372,9 @@ astFieldsLoop: fieldSchema := &SchemaObject{} typeAsString := p.getTypeAsString(astField.Type) typeAsString = strings.TrimLeft(typeAsString, "*") - if strings.HasPrefix(typeAsString, "[]") { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if strings.HasPrefix(typeAsString, "map[]") { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if typeAsString == "time.Time" { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if strings.HasPrefix(typeAsString, "interface{}") { + isSliceOrMap := strings.HasPrefix(typeAsString, "[]") || strings.HasPrefix(typeAsString, "map[]") + isInterface := strings.HasPrefix(typeAsString, "interface{}") + if isSliceOrMap || isInterface || typeAsString == "time.Time" { fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) if err != nil { p.debug(err) @@ -1438,7 +1422,6 @@ astFieldsLoop: if disabled { continue } - // p.debug(">", propertyName) _, exist := structSchema.Properties.Get(propertyName) if exist { continue From 3221cd46d4cc44a9b85ae8357e9a3b1ac49b7693 Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 16:45:17 -0700 Subject: [PATCH 10/13] combine both for loops over astFields into one --- parser.go | 243 ++++++++++++++++++++++++------------------------------ 1 file changed, 108 insertions(+), 135 deletions(-) diff --git a/parser.go b/parser.go index 0bcdba2..9dc1c5a 100644 --- a/parser.go +++ b/parser.go @@ -1229,22 +1229,19 @@ func (p *parser) parseSchemaPropertiesFromStructFields(pkgPath, pkgName string, if astFields == nil { return } - var err error structSchema.Properties = orderedmap.New() if structSchema.DisabledFieldNames == nil { structSchema.DisabledFieldNames = map[string]struct{}{} } -astFieldsLoop: + for _, astField := range astFields { - if len(astField.Names) == 0 { - continue - } fieldSchema := &SchemaObject{} typeAsString := p.getTypeAsString(astField.Type) typeAsString = strings.TrimLeft(typeAsString, "*") isSliceOrMap := strings.HasPrefix(typeAsString, "[]") || strings.HasPrefix(typeAsString, "map[]") isInterface := strings.HasPrefix(typeAsString, "interface{}") if isSliceOrMap || isInterface || typeAsString == "time.Time" { + var err error fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) if err != nil { p.debug(err) @@ -1270,135 +1267,7 @@ astFieldsLoop: } else if isGoTypeOASType(typeAsString) { fieldSchema.Type = goTypesOASTypes[typeAsString] } - - name := astField.Names[0].Name - fieldSchema.FieldName = name - _, disabled := structSchema.DisabledFieldNames[name] - if disabled { - continue - } - - if astField.Tag != nil { - astFieldTag := reflect.StructTag(strings.Trim(astField.Tag.Value, "`")) - tagText := "" - - if tag := astFieldTag.Get("goas"); tag != "" { - tagText = tag - } - tagValues := strings.Split(tagText, ",") - for _, v := range tagValues { - if v == "-" { - structSchema.DisabledFieldNames[name] = struct{}{} - fieldSchema.Deprecated = true - continue astFieldsLoop - } - } - - if tag := astFieldTag.Get("json"); tag != "" { - tagText = tag - } - tagValues = strings.Split(tagText, ",") - isRequired := false - for _, v := range tagValues { - if v == "-" { - structSchema.DisabledFieldNames[name] = struct{}{} - fieldSchema.Deprecated = true - continue astFieldsLoop - } else if v == "required" { - isRequired = true - } else if v != "" && v != "required" && v != "omitempty" { - name = v - } - } - - if tag := astFieldTag.Get("example"); tag != "" { - switch fieldSchema.Type { - case "boolean": - fieldSchema.Example, _ = strconv.ParseBool(tag) - case "integer": - fieldSchema.Example, _ = strconv.Atoi(tag) - case "number": - fieldSchema.Example, _ = strconv.ParseFloat(tag, 64) - case "array": - b, err := json.RawMessage(tag).MarshalJSON() - if err != nil { - fieldSchema.Example = "invalid example" - } else { - sliceOfInterface := []interface{}{} - err := json.Unmarshal(b, &sliceOfInterface) - if err != nil { - fieldSchema.Example = "invalid example" - } else { - fieldSchema.Example = sliceOfInterface - } - } - case "object": - b, err := json.RawMessage(tag).MarshalJSON() - if err != nil { - fieldSchema.Example = "invalid example" - } else { - mapOfInterface := map[string]interface{}{} - err := json.Unmarshal(b, &mapOfInterface) - if err != nil { - fieldSchema.Example = "invalid example" - } else { - fieldSchema.Example = mapOfInterface - } - } - default: - fieldSchema.Example = tag - } - - if fieldSchema.Example != nil && len(fieldSchema.Ref) != 0 { - fieldSchema.Ref = "" - } - } - - if _, ok := astFieldTag.Lookup("required"); ok || isRequired { - structSchema.Required = append(structSchema.Required, name) - } - - if desc := astFieldTag.Get("description"); desc != "" { - fieldSchema.Description = desc - } - } - - structSchema.Properties.Set(name, fieldSchema) - } - for _, astField := range astFields { - if len(astField.Names) > 0 { - continue - } - fieldSchema := &SchemaObject{} - typeAsString := p.getTypeAsString(astField.Type) - typeAsString = strings.TrimLeft(typeAsString, "*") - isSliceOrMap := strings.HasPrefix(typeAsString, "[]") || strings.HasPrefix(typeAsString, "map[]") - isInterface := strings.HasPrefix(typeAsString, "interface{}") - if isSliceOrMap || isInterface || typeAsString == "time.Time" { - fieldSchema, err = p.parseSchemaObject(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug(err) - return - } - } else if !isBasicGoType(typeAsString) { - fieldSchemaSchemeaObjectID, err := p.registerType(pkgPath, pkgName, typeAsString) - if err != nil { - p.debug("parseSchemaPropertiesFromStructFields err:", err) - } else { - fieldSchema.ID = fieldSchemaSchemeaObjectID - schema, ok := p.KnownIDSchema[fieldSchemaSchemeaObjectID] - if ok { - fieldSchema.Type = schema.Type - if schema.Items != nil { - fieldSchema.Items = schema.Items - } - } - fieldSchema.Ref = addSchemaRefLinkPrefix(fieldSchemaSchemeaObjectID) - } - } else if isGoTypeOASType(typeAsString) { - fieldSchema.Type = goTypesOASTypes[typeAsString] - } - // embedded type + // for embedded fields if len(astField.Names) == 0 { if fieldSchema.Properties != nil { for _, propertyName := range fieldSchema.Properties.Keys() { @@ -1431,7 +1300,22 @@ astFieldsLoop: } } } - continue + } else { + name := astField.Names[0].Name + fieldSchema.FieldName = name + _, disabled := structSchema.DisabledFieldNames[name] + if disabled { + continue + } + + newName, skip := parseStructTags(astField, structSchema, fieldSchema, name) + if skip { + continue + } + + name = newName + + structSchema.Properties.Set(name, fieldSchema) } } } @@ -1467,6 +1351,95 @@ func (p *parser) getTypeAsString(fieldType interface{}) string { return fmt.Sprint(fieldType) } +func parseStructTags(astField *ast.Field, structSchema *SchemaObject, fieldSchema *SchemaObject, name string) (newName string, skip bool) { + if astField.Tag != nil { + astFieldTag := reflect.StructTag(strings.Trim(astField.Tag.Value, "`")) + tagText := "" + + if tag := astFieldTag.Get("goas"); tag != "" { + tagText = tag + } + tagValues := strings.Split(tagText, ",") + for _, v := range tagValues { + if v == "-" { + structSchema.DisabledFieldNames[name] = struct{}{} + fieldSchema.Deprecated = true + return "", true + } + } + + if tag := astFieldTag.Get("json"); tag != "" { + tagText = tag + } + tagValues = strings.Split(tagText, ",") + isRequired := false + for _, v := range tagValues { + if v == "-" { + structSchema.DisabledFieldNames[name] = struct{}{} + fieldSchema.Deprecated = true + return "", true + } else if v == "required" { + isRequired = true + } else if v != "" && v != "required" && v != "omitempty" { + name = v + } + } + + if tag := astFieldTag.Get("example"); tag != "" { + switch fieldSchema.Type { + case "boolean": + fieldSchema.Example, _ = strconv.ParseBool(tag) + case "integer": + fieldSchema.Example, _ = strconv.Atoi(tag) + case "number": + fieldSchema.Example, _ = strconv.ParseFloat(tag, 64) + case "array": + b, err := json.RawMessage(tag).MarshalJSON() + if err != nil { + fieldSchema.Example = "invalid example" + } else { + sliceOfInterface := []interface{}{} + err := json.Unmarshal(b, &sliceOfInterface) + if err != nil { + fieldSchema.Example = "invalid example" + } else { + fieldSchema.Example = sliceOfInterface + } + } + case "object": + b, err := json.RawMessage(tag).MarshalJSON() + if err != nil { + fieldSchema.Example = "invalid example" + } else { + mapOfInterface := map[string]interface{}{} + err := json.Unmarshal(b, &mapOfInterface) + if err != nil { + fieldSchema.Example = "invalid example" + } else { + fieldSchema.Example = mapOfInterface + } + } + default: + fieldSchema.Example = tag + } + + if fieldSchema.Example != nil && len(fieldSchema.Ref) != 0 { + fieldSchema.Ref = "" + } + } + + if _, ok := astFieldTag.Lookup("required"); ok || isRequired { + structSchema.Required = append(structSchema.Required, name) + } + + if desc := astFieldTag.Get("description"); desc != "" { + fieldSchema.Description = desc + } + } + + return name, false +} + func (p *parser) debug(v ...interface{}) { if p.Debug { log.Println(v...) From fdf4bb9364da46fd8b135286ab67fe28ca2b3f8a Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 17:16:00 -0700 Subject: [PATCH 11/13] fix double type alias --- parser.go | 8 +++++++- parser_test.go | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index 9dc1c5a..7b4f943 100644 --- a/parser.go +++ b/parser.go @@ -1155,7 +1155,13 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if isGoTypeOASType(p.getTypeAsString(typeSpec.Type)) && schemaObject.Ref == "" { schemaObject.Type = goTypesOASTypes[p.getTypeAsString(typeSpec.Type)] } else if astIdent, ok := typeSpec.Type.(*ast.Ident); ok { - _ = astIdent + // this is for type aliases to custom types + newSchema, err := p.parseSchemaObject(pkgPath, pkgName, astIdent.Name) + if err != nil { + return nil, err + } + schemaObject.Properties = newSchema.Properties + schemaObject.AdditionalProperties = newSchema.AdditionalProperties } else if astStructType, ok := typeSpec.Type.(*ast.StructType); ok { schemaObject.Type = "object" if astStructType.Fields != nil { diff --git a/parser_test.go b/parser_test.go index 28aae9c..49a8533 100644 --- a/parser_test.go +++ b/parser_test.go @@ -125,7 +125,11 @@ func TestExample(t *testing.T) { }, "components": { "schemas": { - "DoubleAlias": {}, + "DoubleAlias": { + "additionalProperties": { + "type": "object" + } + }, "Environment": { "type": "object", "properties": { From 304e96df01873295b9f4ab2d7b6cb021e368cc84 Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 17:34:27 -0700 Subject: [PATCH 12/13] fix formatting --- parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser_test.go b/parser_test.go index 49a8533..776c241 100644 --- a/parser_test.go +++ b/parser_test.go @@ -129,7 +129,7 @@ func TestExample(t *testing.T) { "additionalProperties": { "type": "object" } - }, + }, "Environment": { "type": "object", "properties": { From 882eebc2616c935755ae12cce689c552cccb20f7 Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Mon, 22 Mar 2021 17:36:01 -0700 Subject: [PATCH 13/13] carry over type as well --- parser.go | 1 + parser_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/parser.go b/parser.go index 7b4f943..1ba728b 100644 --- a/parser.go +++ b/parser.go @@ -1160,6 +1160,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if err != nil { return nil, err } + schemaObject.Type = newSchema.Type schemaObject.Properties = newSchema.Properties schemaObject.AdditionalProperties = newSchema.AdditionalProperties } else if astStructType, ok := typeSpec.Type.(*ast.StructType); ok { diff --git a/parser_test.go b/parser_test.go index 776c241..13e62fe 100644 --- a/parser_test.go +++ b/parser_test.go @@ -126,6 +126,7 @@ func TestExample(t *testing.T) { "components": { "schemas": { "DoubleAlias": { + "type": "object", "additionalProperties": { "type": "object" }