diff --git a/go.mod b/go.mod index ac65640..268a136 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/swag v0.22.8 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -86,3 +86,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.1.0 // indirect ) + +replace github.com/getkin/kin-openapi v0.122.0 => /Users/enriquelacal/code/kin-openapi diff --git a/go.sum b/go.sum index 630c43e..7a49b9e 100644 --- a/go.sum +++ b/go.sum @@ -21,14 +21,12 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= -github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= +github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -135,8 +133,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/ffapi/openapi3.go b/pkg/ffapi/openapi3.go index 2bcee7f..a50f9fa 100644 --- a/pkg/ffapi/openapi3.go +++ b/pkg/ffapi/openapi3.go @@ -188,21 +188,34 @@ func (sg *SwaggerGen) ffTagHandler(ctx context.Context, route *Route, name strin } func (sg *SwaggerGen) addCustomType(t reflect.Type, schema *openapi3.Schema) { - typeString := "string" + typeString := openapi3.TypeString switch t.Name() { case "UUID": - schema.Type = typeString + schema.Type = &openapi3.Types{typeString} schema.Format = "uuid" case "FFTime": - schema.Type = typeString + schema.Type = &openapi3.Types{typeString} schema.Format = "date-time" case "Bytes32": - schema.Type = typeString + schema.Type = &openapi3.Types{typeString} schema.Format = "byte" case "FFBigInt": - schema.Type = typeString + schema.Type = &openapi3.Types{typeString} case "JSONAny": - schema.Type = "" + schema.Type = &openapi3.Types{} + } +} + +func constructExportComponentSchemasOptions(route *Route) openapi3gen.ExportComponentSchemasOptions { + exportTopLevelSchema := false + if route.ExportTopLevelComponentSchema != nil { + exportTopLevelSchema = *route.ExportTopLevelComponentSchema + } + + return openapi3gen.ExportComponentSchemasOptions{ + ExportComponentSchemas: true, + ExportTopLevelSchema: exportTopLevelSchema, + ExportGenerics: true, } } @@ -216,13 +229,13 @@ func (sg *SwaggerGen) addInput(ctx context.Context, doc *openapi3.T, route *Rout switch { case route.JSONInputSchema != nil: schemaRef, err = route.JSONInputSchema(ctx, func(obj interface{}) (*openapi3.SchemaRef, error) { - return openapi3gen.NewSchemaRefForValue(obj, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer)) + return openapi3gen.NewSchemaRefForValue(obj, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer), openapi3gen.CreateComponentSchemas(constructExportComponentSchemasOptions(route))) }) if err != nil { panic(fmt.Sprintf("invalid schema: %s", err)) } case route.JSONInputValue != nil: - schemaRef, err = openapi3gen.NewSchemaRefForValue(route.JSONInputValue(), doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer)) + schemaRef, err = openapi3gen.NewSchemaRefForValue(route.JSONInputValue(), doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer), openapi3gen.CreateComponentSchemas(constructExportComponentSchemasOptions(route))) if err != nil { panic(fmt.Sprintf("invalid schema: %s", err)) } @@ -236,7 +249,7 @@ func (sg *SwaggerGen) addFormInput(ctx context.Context, op *openapi3.Operation, props := openapi3.Schemas{ "filename.ext": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Type: "string", + Type: &openapi3.Types{"string"}, Format: "binary", }, }, @@ -245,7 +258,7 @@ func (sg *SwaggerGen) addFormInput(ctx context.Context, op *openapi3.Operation, props[fp.Name] = &openapi3.SchemaRef{ Value: &openapi3.Schema{ Description: i18n.Expand(ctx, i18n.APISuccessResponse), - Type: "string", + Type: &openapi3.Types{"string"}, }, } } @@ -253,7 +266,7 @@ func (sg *SwaggerGen) addFormInput(ctx context.Context, op *openapi3.Operation, op.RequestBody.Value.Content["multipart/form-data"] = &openapi3.MediaType{ Schema: &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Type: "object", + Type: &openapi3.Types{"object"}, Properties: props, }, }, @@ -287,7 +300,7 @@ func (sg *SwaggerGen) addOutput(ctx context.Context, doc *openapi3.T, route *Rou switch { case route.JSONOutputSchema != nil: schemaRef, err = route.JSONOutputSchema(ctx, func(obj interface{}) (*openapi3.SchemaRef, error) { - return openapi3gen.NewSchemaRefForValue(obj, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer)) + return openapi3gen.NewSchemaRefForValue(obj, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer), openapi3gen.CreateComponentSchemas(constructExportComponentSchemasOptions(route))) }) if err != nil { panic(fmt.Sprintf("invalid schema: %s", err)) @@ -295,7 +308,7 @@ func (sg *SwaggerGen) addOutput(ctx context.Context, doc *openapi3.T, route *Rou case route.JSONOutputValue != nil: outputValue := route.JSONOutputValue() if outputValue != nil { - schemaRef, err = openapi3gen.NewSchemaRefForValue(outputValue, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer)) + schemaRef, err = openapi3gen.NewSchemaRefForValue(outputValue, doc.Components.Schemas, openapi3gen.SchemaCustomizer(schemaCustomizer), openapi3gen.CreateComponentSchemas(constructExportComponentSchemasOptions(route))) if err != nil { panic(fmt.Sprintf("invalid schema: %s", err)) } @@ -339,15 +352,15 @@ func (sg *SwaggerGen) addParamInternal(ctx context.Context, op *openapi3.Operati exampleValue = example } value := &openapi3.Schema{ - Type: "string", + Type: &openapi3.Types{"string"}, Default: defValue, Example: exampleValue, } if isArray { - value.Type = "array" + value.Type = &openapi3.Types{"array"} value.Items = &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Type: "string", + Type: &openapi3.Types{"string"}, Default: defValue, Example: exampleValue, }, diff --git a/pkg/ffapi/openapi3_test.go b/pkg/ffapi/openapi3_test.go index f44911a..ffe2e96 100644 --- a/pkg/ffapi/openapi3_test.go +++ b/pkg/ffapi/openapi3_test.go @@ -182,11 +182,17 @@ func TestOpenAPI3SwaggerGen(t *testing.T) { }, SupportFieldRedaction: true, }).Generate(context.Background(), testRoutes) - err := doc.Validate(context.Background()) - assert.NoError(t, err) + sl := openapi3.NewLoader() b, err := yaml.Marshal(doc) assert.NoError(t, err) + + doc, err = sl.LoadFromData(b) + assert.NoError(t, err) + + err = doc.Validate(sl.Context) + assert.NoError(t, err) + fmt.Print(string(b)) } @@ -427,17 +433,24 @@ func TestBaseURLVariables(t *testing.T) { }, }, }).Generate(context.Background(), testRoutes) - err := doc.Validate(context.Background()) + + sl := openapi3.NewLoader() + b, err := yaml.Marshal(doc) + assert.NoError(t, err) + + doc, err = sl.LoadFromData(b) + assert.NoError(t, err) + + // Validate doesn't like references + // so LoadFromData resolves those references + // before we validate + err = doc.Validate(sl.Context) assert.NoError(t, err) server := doc.Servers[0] if assert.Contains(t, server.Variables, "param") { assert.Equal(t, "default-value", server.Variables["param"].Default) } - - b, err := yaml.Marshal(doc) - assert.NoError(t, err) - fmt.Print(string(b)) } func TestCheckObjectDocumented(t *testing.T) { diff --git a/pkg/ffapi/routes.go b/pkg/ffapi/routes.go index 12578ef..21685e0 100644 --- a/pkg/ffapi/routes.go +++ b/pkg/ffapi/routes.go @@ -78,6 +78,9 @@ type Route struct { Tag string // Extensions allows extension of the route struct by individual microservices Extensions interface{} + // ExportTopLevelComponentSchema defines whether the top level schema for the output and input should be exported + // by default it's set to true + ExportTopLevelComponentSchema *bool } // PathParam is a description of a path parameter