-
Notifications
You must be signed in to change notification settings - Fork 88
Support for openapi v3/v3.1 schema formats #1291 #1494
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
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for springwolf-ui canceled.
|
| } | ||
|
|
||
| String type = schema.getType(); | ||
| // schema may be an openapi v3 or v3.1 schema. While v3 uses an simple 'type' field, v3.1 supports a set of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
building examples from types happend to be a little bit 'tricky' because openapi v3 and v3.1 changed the field for the type(s). I moved the "type-lookup" in an extra method 'getTypeForExampleValue'.
| public ExtractedSchemas resolveSchema(Type type, String contentType, SchemaFormat schemaFormat) { | ||
| String actualContentType = | ||
| StringUtils.isBlank(contentType) ? properties.getDocket().getDefaultContentType() : contentType; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is one of the main implementation point: Decision, which ModelConverts to use. AsyncApi is currently based on openapi-V3
| * @return the resulting AsnycApi SchemaObject | ||
| */ | ||
| public SchemaObject mapSchema(Schema value) { | ||
| public ComponentSchema mapSchema(Schema swaggerSchema, SchemaFormat schemaFormat) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the point where the generated Swagger-schema (from swagger ModelConverters) is mapped to the target schema format. For openapi v3/v3.1, no transformation is necessary, only wrapping the schema into an MultiFormatschema.
For asyncapi schemaformat (the default), the existing mapping logic is used (mapSwaggerSchemaToAsyncApiSchema
| @JsonSubTypes.Type(value = Dog.class, name = "dog"), | ||
| @JsonSubTypes.Type(value = Cat.class, name = "cat"), | ||
| }) | ||
| @Schema( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extended the Pet Superclass with @Schema Annotation and discriminator mapping Infos. These Infos are ignored for asyncapi schemaformat (because asyncapi does not support custom mappings) - but are detected and used for openapi schema formats.
d8d540e to
ad80cfa
Compare
timonback
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sam0r040 and I reviewed the PR. We enjoyed the PR very much. The PR is surprisingly sleek and it shows that you have understood and found the best place for integrating this feature.
We added some comments, suggestions. If there are questions, let us know. Some comments probably also reflect the Springwolf code style.
FYI: We committed & pushed a couple smaller changes (i.e. avoiding breaking API change)
| Schema rootSchema, Map<String, Schema> referencedSchemas, SchemaFormat schemaFormat) { | ||
| ComponentSchema rootComponentSchema = swaggerSchemaUtil.mapSchemaOrRef(rootSchema, schemaFormat); | ||
| Map<String, ComponentSchema> referencedComponentSchemas = | ||
| swaggerSchemaUtil.mapSchemasMap(referencedSchemas, schemaFormat); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch, that the empty hashmap is not used
| PayloadSchemaFormat payloadSchemaFormat = | ||
| springwolfConfigProperties.getDocket().getPayloadSchemaFormat(); | ||
| this.payloadSchemaFormat = payloadSchemaFormat.getSchemaFormat(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personal opinion: prefer auto-generated constructor and thereby avoiding the substitution of configProperties
| */ | ||
| public SchemaObject mapSchema(Schema value) { | ||
| public ComponentSchema mapSchema(Schema swaggerSchema, SchemaFormat schemaFormat) { | ||
| return switch (schemaFormat) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: the current mix up of openapi 3.0 and 3.1 schemas still needs to be addressed in the mapper (other open issue, out of scope for this pr)
| void classWithSchemaAnnotationWithAsyncapiSchemaformat() { | ||
| ComponentSchema schema = schemaService | ||
| .resolveSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant") | ||
| .resolveSchema(ClassWithSchemaAnnotation.class, "content-type-not-relevant", SchemaFormat.ASYNCAPI_V3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
review: does class need more test cases or is it fine to just add the method parameter?
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| public class AsyncApiDocumentWithOpenApiV31SchemaFormatIntegrationTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
review: do we need all the tests on this level?
I wonder if one happy path test case is sufficient. Feels like a lot of test duplication
| @@ -0,0 +1,44 @@ | |||
| // SPDX-License-Identifier: Apache-2.0 | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure whether the jms example is the best place for these tests, as it has not a lot of dtos.
So far, we have mostly used the kafka example.
Similiarly, we can use the new StandaloneApplication test, which executes faster because it does not need a full spring boot context. Environment variables (for the schema format) can be injected using the builder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One second thought, we believe that we can skip this test.
The examples are particular show cases of the main functionality - not all types of configurations possible.
Second (out of scope), we can benefit from integration tests in core with the asyncapi.json file checked in (currently tested manual in the two classes that you duplicated).
Also, the actual mapping is done by swaggers ModelConverter (a library that we do not control). Therefore, we see it not as much in our test scope.
| public class SwaggerSchemaUtil { | ||
|
|
||
| public Map<String, SchemaObject> mapSchemasMap(Map<String, Schema> schemaMap) { | ||
| public Map<String, ComponentSchema> mapSchemasMap(Map<String, Schema> schemaMap, SchemaFormat schemaFormat) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered using the schemaFormat as a global setting and then injecting the SpringwolfConfigProperties?
Over the time the SwaggerSchemaUtil has grown and we could imagine that the SpringwolfConfigProperties become a constructor argument when rename to i.e. SwaggerSchemaMapper. (It does not feel like an util anymore)
| private final ModelConverters converter = ModelConverters.getInstance(); | ||
| private final ModelConverters converter_openapi30 = ModelConverters.getInstance(false); | ||
| private final ModelConverters converter_openapi31 = ModelConverters.getInstance(true); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered moving these two fields into a ModelConvertersProvider?
We stumpled upon this, because the related tests verify that the correct model provider switch case is used, but not that the actual mapping work as expected. Still, actual mapping is probably best used in an integration test anyway.
| /** | ||
| * Enumeration defining the supported payload schema formats, for use in SpringwolfConfigProperties. | ||
| */ | ||
| public enum PayloadSchemaFormat { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not sure if it a good idea to directly map the payload schema into the SchemaFormat enum, which has more types.
It couples the values of this enum to the other other and leaves less room for configuration changes.
Similarly, it feels odd in the switch expression to have an else branch, that can never be reached - at this moment in time, but is required technically for the compiler.
Suggestion:
ASYNCAPI_V3, OPENAPI_V3, OPENAPI_V3_1;
And the PayloadSchemaFormat is used in the code, including the switch expression
| String url = "/springwolf/docs"; | ||
| String actual = restTemplate.getForObject(url, String.class); | ||
| String actualPatched = actual.replace("localhost:61616", "activemq:61616"); | ||
| Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Files.writeString(Path.of("src", "test", "resources", "asyncapi.actual.json"), actualPatched); | |
| Files.writeString(Path.of("src", "test", "resources", "asyncapi.openapiv31.actual.json"), actualPatched); |
23e6862 to
98a085c
Compare
…chema, change back public api method from registerSimpleSchema to registerSchema to prevent breaking change, rename SwaggerSchemaService.postProcessSimpleSchema to postProcessSchemaWithoutRef to clarify the intention of the method Co-authored-by: Timon Back <timonback@users.noreply.github.com>
98a085c to
f3b6093
Compare
I introduced a new Property
springwolf.docket.payload-schema-formatwhich is an enum and supports currentlyWith the default-setting (asyncapi_v3), SpringWolf behaves as before.
Enabling openapi_v3 or _v3_1 leads to a AsyncApi where the payload schemas (not the header schemas!) are formatted with openapi v3/v3.1 format. This is possible and allowed because the AsyncApi Spec supports the definition of alternative schema formats.
The openapi v3/v3.1 schema formats are especially helpful when it comes to polymorphic schemas with custom discrimiantor mappings (which are not possible to describe with default asyncapi schema format).