Contributions
Using correct tag names in Model Resolver of swagger-core when using JAXB annotated Java POJOS
Hello Dear Swagger OS Tools Community, We utilize a build process, where we generate JAXB-Annotated POJOs from XSD Schema files, then generate / serve OpenAPI files through Swagger / SpringDoc. This is not the most optimal approach, but since we work with ISO 20022 messages in the banking sector that still works with XSD files rather than JSON Schema and friends, we have to stick with it. Now I encountered on problem with the Model Resolver in the swagger-core for which I need your help / opinion. To illustrate this, lets consider our JAXB POJOS to look like this: package io.swagger.v3.core.oas.models.xmltest; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "Relocation") public class Relocation { @XmlElement(name = "Name") public String name; @XmlElement(name = "OldAddress") public Address oldAddress; @XmlElement(name = "NewAddress") public Address newAddress; } And: package io.swagger.v3.core.oas.models.xmltest; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; @XmlType(name = "RelocationAddress", propOrder = { "Street", "City" }) public class ReolcationAddress { @XmlElement(name = "Street") public String street; @XmlElement(name = "City") public String city; } During model resolution, the Model resolver will automatically detect the relevant annotation and build an internal Schema object out of it: {Relocation=class Schema { type: object format: null $ref: null description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: {name=class StringSchema { class Schema { type: string format: null $ref: null description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: null additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: Name namespace: null prefix: null attribute: null wrapped: null } } }, oldAddress=class Schema { type: null format: null $ref: #/components/schemas/RelocationAddress description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: null additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: OldAddress namespace: null prefix: null attribute: null wrapped: null } }, newAddress=class Schema { type: null format: null $ref: #/components/schemas/RelocationAddress description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: null additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: NewAddress namespace: null prefix: null attribute: null wrapped: null } }} additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: Relocation namespace: https://www.openapis.org/test/nested prefix: null attribute: null wrapped: null } }, RelocationAddress=class Schema { type: object format: null $ref: null description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: {street=class StringSchema { class Schema { type: string format: null $ref: null description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: null additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: Street namespace: null prefix: null attribute: null wrapped: null } } }, city=class StringSchema { class Schema { type: string format: null $ref: null description: null title: null multipleOf: null maximum: null exclusiveMaximum: null minimum: null exclusiveMinimum: null maxLength: null minLength: null pattern: null maxItems: null minItems: null uniqueItems: null maxProperties: null minProperties: null required: null not: null properties: null additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: class XML { name: City namespace: null prefix: null attribute: null wrapped: null } } }} additionalProperties: null nullable: null readOnly: null writeOnly: null example: null externalDocs: null deprecated: null discriminator: null xml: null } As you can see, the model successfully extracts the necessary XML Object based on the XmlElement annotation and puts it into the properties list within the Relocation model. However, this information gets lost as soon as it generates an actual OAS Schema out of it: Relocation: type: object properties: name: type: string oldAddress: $ref: '#/components/schemas/RelocationAddress' newAddress: $ref: '#/components/schemas/RelocationAddress' xml: name: Relocation namespace: https://www.openapis.org/test/nested RelocationAddress: type: object properties: street: type: string xml: name: Street city: type: string xml: name: City So reading that schema would mean that the object of type 'Relocation' will have all upper case first letter except 'oldAddress' and 'newAddress', because the XML Object is missing there. This is, however, in-sync with the OAS specification, as it would forbid the use of an XML Object when '$ref' is used at the same time. Just moving the XML Object from property to type definition would not solve it, as I use 'RelocationAddress' on two occasions with different Tag Names (OldAddress and NewAddress). This example might look trivial at first, but imagine you have something like 'CdtrAcc' and 'DbtrAcc' as XML tags and the actual Java properties are called 'creditorAccount' and 'debtorAccount'. Now, my bold head came up with two potential approaches: 1) Duplicate the referenced type every time a conflict happens: Relocation: type: object properties: name: type: string oldAddress: $ref: '#/components/schemas/OldAddress_RelocationAddress' newAddress: $ref: '#/components/schemas/NewAddress_RelocationAddress' xml: name: Relocation namespace: https://www.openapis.org/test/nested OldAddress_RelocationAddress: type: object xml: name: OldAddress properties: street: type: string xml: name: Street city: type: string xml: name: City NewAddress_RelocationAddress: type: object xml: name: NewAddress properties: street: type: string xml: name: Street city: type: string xml: name: City This would give the freedom to have different property names for specific tag names, but has also the disadvantage to potentially generate larger OAS files with unnecessary duplication. 2) Always use the Tag name as property name on collision: Relocation: type: object properties: name: type: string OldAddress: $ref: '#/components/schemas/RelocationAddress' NewAddress: $ref: '#/components/schemas/RelocationAddress' xml: name: Relocation namespace: https://www.openapis.org/test/nested RelocationAddress: type: object properties: street: type: string xml: name: Street city: type: string xml: name: City We could tell the model resolver to just overwrite the property definition with its XML tag definition from the XmlElement annotation. The resulting model would be inconsequential for the API consumer, and the model generated from that OAS file would look like "normal code'. The only drawback is that the original 'property" information gets lost during this process. On the other hand, that information might not be that useful anyway from an API design standpoint. I would personally love to see option 2) implemented, or at least somehow an option to control this generation. Please recall that since we generate JAXB POJOs from an XSD file, we don't have that much of control on the Java property names generated from the XJC tool. I welcome any alternative suggestion.780Views0likes0CommentsSupport for XmlSchema JAXB annotation
Hello all, does anything speak against supporting XmlSchema annotations during model resolution? We have in our projects the following setup: 1. Design data types in XSD files 2. Generate Java POJOs annotated with JAXB annotations via xjc tool 3. Generate / Serve OAS files from annotated POJOs using springdoc (this uses swagger-core under the hood). I notice that swagger-core does not read from the XmlSchema, which leads to missing information in the XML Object of the resulting OAS file. Already created a PR to address this:https://github.com/swagger-api/swagger-core/pull/4278Solved725Views0likes1Comment