From 5319534a421f40965cf041d281dc9d04c4b799e9 Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Fri, 5 Mar 2021 11:44:02 -0800 Subject: [PATCH 1/2] support map types using additionalProperties --- example/foo.go | 9 +- oas.go | 18 +-- parser.go | 5 +- parser_test.go | 335 +++++++++++++++++++++++++------------------------ 4 files changed, 191 insertions(+), 176 deletions(-) diff --git a/example/foo.go b/example/foo.go index 3bb6f58..bd7dc62 100644 --- a/example/foo.go +++ b/example/foo.go @@ -7,11 +7,14 @@ import ( type FooResponse struct { ID string `json:"id"` - Bar string `json:"bar"` - Baz string `json:"baz"` - startDate time.Time `json:"startDate"` + StartDate time.Time `json:"startDate"` Msg json.RawMessage `json:"msg"` InnerFoos []InnerFoo `json:"foo"` + Environments map[string]Environment `json:"environments"` +} + +type Environment struct { + Name string `json:"name"` } // @Title Get all foos diff --git a/oas.go b/oas.go index 94e1d34..94da8ae 100644 --- a/oas.go +++ b/oas.go @@ -136,14 +136,15 @@ type SchemaObject struct { FieldName string `json:"-"` // For goas DisabledFieldNames map[string]struct{} `json:"-"` // For goas - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` - Required []string `json:"required,omitempty"` - Properties *orderedmap.OrderedMap `json:"properties,omitempty"` - Description string `json:"description,omitempty"` - Items *SchemaObject `json:"items,omitempty"` // use ptr to prevent recursive error - Example interface{} `json:"example,omitempty"` - Deprecated bool `json:"deprecated,omitempty"` + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` + Required []string `json:"required,omitempty"` + Properties *orderedmap.OrderedMap `json:"properties,omitempty"` + AdditionalProperties *SchemaObject `json:"additionalProperties,omitempty"` + Description string `json:"description,omitempty"` + Items *SchemaObject `json:"items,omitempty"` // use ptr to prevent recursive error + Example interface{} `json:"example,omitempty"` + Deprecated bool `json:"deprecated,omitempty"` // Ref is used when SchemaObject is as a ReferenceObject Ref string `json:"$ref,omitempty"` @@ -167,7 +168,6 @@ type SchemaObject struct { // OneOf // AnyOf // Not - // AdditionalProperties // Description // Default // Nullable diff --git a/parser.go b/parser.go index 04102b9..d488a7b 100644 --- a/parser.go +++ b/parser.go @@ -1057,15 +1057,14 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb itemTypeName := typeName[5:] schema, ok := p.KnownIDSchema[genSchemeaObjectID(pkgName, itemTypeName)] if ok { - schemaObject.Items = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} + schemaObject.AdditionalProperties = &SchemaObject{Ref: addSchemaRefLinkPrefix(schema.ID)} return &schemaObject, nil } schemaProperty, err := p.parseSchemaObject(pkgPath, pkgName, itemTypeName) if err != nil { return nil, err } - schemaObject.Properties = orderedmap.New() - schemaObject.Properties.Set("key", schemaProperty) + schemaObject.AdditionalProperties = schemaProperty return &schemaObject, nil } else if typeName == "time.Time" { schemaObject.Type = "string" diff --git a/parser_test.go b/parser_test.go index c3e8158..3b65eba 100644 --- a/parser_test.go +++ b/parser_test.go @@ -14,177 +14,190 @@ func TestExample(t *testing.T) { err = p.parse() require.NoError(t, err) - bts, err := json.Marshal(p.OpenAPI) + bts, err := json.MarshalIndent(p.OpenAPI, "", " ") require.NoError(t, err) expected := ` { - "openapi":"3.0.0", - "info":{ - "title":"LaunchDarkly REST API", - "description":"Build custom integrations with the LaunchDarkly REST API", - "contact":{ - "name":"LaunchDarkly Technical Support Team", - "url":"https://support.launchdarkly.com", - "email":"support@launchdarkly.com" - }, - "license":{ - "name":"Apache 2.0", - "url":"https://www.apache.org/licenses/LICENSE-2.0" - }, - "version":"2.0" - }, - "servers":[ - { - "url":"https://app.launchdarkly.com" - } - ], - "paths":{ - "/api/v2/foo":{ - "get":{ - "responses":{ - "200":{ - "description":"Successful foo response", - "content":{ - "application/json":{ - "schema":{ - "$ref":"#/components/schemas/FooResponse" - } - } - } - }, - "401":{ - "description":"Invalid access token" - }, - "403":{ - "description":"Forbidden" - }, - "404":{ - "description":"Invalid resource identifier" - } + "components": { + "schemas": { + "Environment": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" }, - "summary":"Get all foos", - "description":" Get all foos" - }, - "put":{ - "responses":{ - "200":{ - "description":"Successful foo response", - "content":{ - "application/json":{ - "schema":{ - "$ref":"#/components/schemas/FooResponse" - } - } - } - }, - "401":{ - "description":"Invalid access token" - }, - "403":{ - "description":"Forbidden" - }, - "404":{ - "description":"Invalid resource identifier" - } + "FooResponse": { + "properties": { + "environments": { + "additionalProperties": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "object" + }, + "foo": { + "items": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "msg": { + "type": "object" + }, + "startDate": { + "format": "date-time", + "type": "string" + } + }, + "type": "object" }, - "summary":"Put foo", - "description":" Overwrite a foo" - } - }, - "/api/v2/foo/{id}/inner":{ - "put":{ - "responses":{ - "200":{ - "description":"Successful innerfoo response", - "content":{ - "application/json":{ - "schema":{ - "$ref":"#/components/schemas/InnerFoo" - } - } - } - }, - "401":{ - "description":"Invalid access token" - }, - "403":{ - "description":"Forbidden" - }, - "404":{ - "description":"Invalid resource identifier" - } + "InnerFoo": { + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "type": "object" + } + }, + "securitySchemes": { + "ApiKey": { + "in": "header", + "name": "Authorization", + "type": "apiKey" + } + } + }, + "info": { + "contact": { + "email": "support@launchdarkly.com", + "name": "LaunchDarkly Technical Support Team", + "url": "https://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", + "paths": { + "/api/v2/foo": { + "get": { + "description": " Get all foos", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FooResponse" + } + } + }, + "description": "Successful foo response" + }, + "401": { + "description": "Invalid access token" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Invalid resource identifier" + } + }, + "summary": "Get all foos" }, - "summary":"Get inner foos", - "description":" Get Inner Foos" - } - } - }, - "components":{ - "schemas":{ - "FooResponse":{ - "type":"object", - "properties":{ - "id":{ - "type":"string" - }, - "bar":{ - "type":"string" - }, - "baz":{ - "type":"string" - }, - "startDate":{ - "type":"string", - "format":"date-time" - }, - "msg":{ - "type":"object" - }, - "foo":{ - "type":"array", - "items":{ - "type":"object", - "properties":{ - "a":{ - "type":"string" + "put": { + "description": " Overwrite a foo", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FooResponse" + } + } }, - "b":{ - "type":"string" - } - } - } - } + "description": "Successful foo response" + }, + "401": { + "description": "Invalid access token" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Invalid resource identifier" + } + }, + "summary": "Put foo" } - }, - "InnerFoo":{ - "type":"object", - "properties":{ - "a":{ - "type":"string" - }, - "b":{ - "type":"string" - } + }, + "/api/v2/foo/{id}/inner": { + "put": { + "description": " Get Inner Foos", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InnerFoo" + } + } + }, + "description": "Successful innerfoo response" + }, + "401": { + "description": "Invalid access token" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Invalid resource identifier" + } + }, + "summary": "Get inner foos" } - } - }, - "securitySchemes":{ - "ApiKey":{ - "type":"apiKey", - "in":"header", - "name":"Authorization" - } - } - }, - "security":[ - { - "ApiKey":[ - "read", - "write" - ] - } - ] + } + }, + "security": [ + { + "ApiKey": [ + "read", + "write" + ] + } + ], + "servers": [ + { + "url": "https://app.launchdarkly.com" + } + ] } ` require.JSONEq(t, expected, string(bts)) From 1b74958208060b6affbec956c4fdb5cda7b8a118 Mon Sep 17 00:00:00 2001 From: Raymond Ho Date: Fri, 5 Mar 2021 11:47:22 -0800 Subject: [PATCH 2/2] fix formatting --- parser.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/parser.go b/parser.go index d488a7b..6042bbf 100644 --- a/parser.go +++ b/parser.go @@ -235,7 +235,6 @@ func (p *parser) parse() error { return nil } - func (p *parser) CreateOASFile(path string) error { if err := p.parse(); err != nil { return err @@ -1064,7 +1063,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb if err != nil { return nil, err } - schemaObject.AdditionalProperties = schemaProperty + schemaObject.AdditionalProperties = schemaProperty return &schemaObject, nil } else if typeName == "time.Time" { schemaObject.Type = "string" @@ -1137,7 +1136,7 @@ func (p *parser) parseSchemaObject(pkgPath, pkgName, typeName string) (*SchemaOb return &schemaObject, nil } - log.Fatalf("Cannot find definition of guess %s ast.TypeSpec in package %s. " + + log.Fatalf("Cannot find definition of guess %s ast.TypeSpec in package %s. "+ "If definition is in a vendor dependency, try running `go mod tidy && go mod vendor`", guessTypeName, guessPkgName) } @@ -1502,7 +1501,7 @@ func (p *parser) debugf(format string, args ...interface{}) { } } -func sortedPackageKeys(m map[string]*ast.Package) ([]string) { +func sortedPackageKeys(m map[string]*ast.Package) []string { keys := make([]string, len(m)) i := 0 for k, _ := range m { @@ -1513,7 +1512,7 @@ func sortedPackageKeys(m map[string]*ast.Package) ([]string) { return keys } -func sortedFileKeys(m map[string]*ast.File) ([]string) { +func sortedFileKeys(m map[string]*ast.File) []string { keys := make([]string, len(m)) i := 0 for k, _ := range m {