Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: export input and output schemas to components in OpenAPI document #131

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
45 changes: 29 additions & 16 deletions pkg/ffapi/openapi3.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"strings"
"time"

"github.com/getkin/kin-openapi/openapi3"

Check failure on line 31 in pkg/ffapi/openapi3.go

View workflow job for this annotation

GitHub Actions / build

github.com/getkin/[email protected]: replacement directory /Users/enriquelacal/code/kin-openapi does not exist
"github.com/getkin/kin-openapi/openapi3gen"

Check failure on line 32 in pkg/ffapi/openapi3.go

View workflow job for this annotation

GitHub Actions / build

github.com/getkin/[email protected]: replacement directory /Users/enriquelacal/code/kin-openapi does not exist
"github.com/hyperledger/firefly-common/pkg/config"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly-common/pkg/i18n"
Expand Down Expand Up @@ -188,21 +188,34 @@
}

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,
}
}

Expand All @@ -216,13 +229,13 @@
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))
}
Expand All @@ -236,7 +249,7 @@
props := openapi3.Schemas{
"filename.ext": &openapi3.SchemaRef{
Value: &openapi3.Schema{
Type: "string",
Type: &openapi3.Types{"string"},
Format: "binary",
},
},
Expand All @@ -245,15 +258,15 @@
props[fp.Name] = &openapi3.SchemaRef{
Value: &openapi3.Schema{
Description: i18n.Expand(ctx, i18n.APISuccessResponse),
Type: "string",
Type: &openapi3.Types{"string"},
},
}
}

op.RequestBody.Value.Content["multipart/form-data"] = &openapi3.MediaType{
Schema: &openapi3.SchemaRef{
Value: &openapi3.Schema{
Type: "object",
Type: &openapi3.Types{"object"},
Properties: props,
},
},
Expand Down Expand Up @@ -287,15 +300,15 @@
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))
}
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))
}
Expand Down Expand Up @@ -339,15 +352,15 @@
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,
},
Expand Down
27 changes: 20 additions & 7 deletions pkg/ffapi/openapi3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Expand Down
3 changes: 3 additions & 0 deletions pkg/ffapi/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading