springdoc-openapi icon indicating copy to clipboard operation
springdoc-openapi copied to clipboard

Custom ModelResolver and TypeNameResolver results in unexpected behaviour

Open razum90 opened this issue 2 months ago • 0 comments

Hi!

I am using:

  • Spring Boot 3.2.4
  • Java 21
  • springdoc-openapi-starter-webmvc-ui 2.5.0

When implementing a custom ModelResolver and TypeNameResolver the behaviour of entities wrapped in a ResponseEntity changes.

Here is my implementation:

@Configuration
public class SwaggerAutoConfiguration {

    @Bean
    public CustomConverter customConverter(ObjectMapper objectMapper) {
        return new CustomConverter(objectMapper);
    }

    static class CustomTypeNameResolver extends TypeNameResolver {
        public CustomTypeNameResolver() {
            setUseFqn(true);
        }
    }

    public static class CustomConverter extends ModelResolver {

        public CustomConverter(ObjectMapper mapper) {
            super(mapper, new CustomTypeNameResolver());
        }
    }
}

I am using the above setup because I will have logic to modify the model names.

I would expect the above only to set useFqn to true, and the rest would stay the same. However that does not seem like the case for entities wrapped a ResponseEntity. Below is an example.

    @Operation(summary = "...")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200"
            ),
            @ApiResponse(
                    responseCode = "207",
                    description = "Partial success"
            ),
            @ApiResponse(
                    responseCode = "409",
                    description = "Failure"
            )
    })
    @PostMapping(path = AUTHENTICATE_PAYMENT)
    public ResponseEntity<AuthenticatePaymentResponse> authenticatePayment(
            @Valid @RequestBody AuthenticatePaymentRequest authenticatePaymentRequest,
            SomeOtherParameters someOtherParameters
    ) {
        ...
    }

After adding the configuration, the response body changes in Swagger for all defined ApiResponses on the endpoint like so:

{
  "headers": {
    "acceptLanguage": [
      {
        "range": "string",
        "weight": 0
      }
    ],
    "acceptPatch": [
      {
        "type": "string",
        "subtype": "string",
        "parameters": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        },
        "qualityValue": 0,
        "wildcardType": true,
        "wildcardSubtype": true,
        "subtypeSuffix": "string",
        "charset": {
          "registered": true
        },
        "concrete": true
      }
    ],
    "accessControlAllowCredentials": true,
    "accessControlExposeHeaders": [
      "string"
    ],
    "accessControlRequestHeaders": [
      "string"
    ],
    "accessControlRequestMethod": {},
    "acceptLanguageAsLocales": [
      {
        "language": "string",
        "displayName": "string",
        "country": "string",
        "variant": "string",
        "script": "string",
        "unicodeLocaleAttributes": [
          "string"
        ],
        "unicodeLocaleKeys": [
          "string"
        ],
        "displayLanguage": "string",
        "displayScript": "string",
        "displayCountry": "string",
        "displayVariant": "string",
        "extensionKeys": [
          "string"
        ],
        "iso3Language": "string",
        "iso3Country": "string"
      }
    ],
    "accessControlAllowHeaders": [
      "string"
    ],
    "accessControlAllowMethods": [
      {}
    ],
    "accessControlAllowOrigin": "string",
    "accessControlMaxAge": 0,
    "acceptCharset": [
      {
        "registered": true
      }
    ],
    "connection": [
      "string"
    ],
    "ifNoneMatch": [
      "string"
    ],
    "ifUnmodifiedSince": 0,
    "etag": "string",
    "expires": 0,
    "ifMatch": [
      "string"
    ],
    "allow": [
      {}
    ],
    "accept": [
      {
        "type": "string",
        "subtype": "string",
        "parameters": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        },
        "qualityValue": 0,
        "wildcardType": true,
        "wildcardSubtype": true,
        "subtypeSuffix": "string",
        "charset": {
          "registered": true
        },
        "concrete": true
      }
    ],
    "origin": "string",
    "pragma": "string",
    "range": [
      {}
    ],
    "upgrade": "string",
    "vary": [
      "string"
    ],
    "contentDisposition": {
      "type": "string",
      "name": "string",
      "filename": "string",
      "charset": {
        "registered": true
      },
      "size": 0,
      "creationDate": "2024-04-15T19:18:37.770Z",
      "modificationDate": "2024-04-15T19:18:37.770Z",
      "readDate": "2024-04-15T19:18:37.770Z",
      "attachment": true,
      "formData": true,
      "inline": true
    },
    "contentLanguage": {
      "language": "string",
      "displayName": "string",
      "country": "string",
      "variant": "string",
      "script": "string",
      "unicodeLocaleAttributes": [
        "string"
      ],
      "unicodeLocaleKeys": [
        "string"
      ],
      "displayLanguage": "string",
      "displayScript": "string",
      "displayCountry": "string",
      "displayVariant": "string",
      "extensionKeys": [
        "string"
      ],
      "iso3Language": "string",
      "iso3Country": "string"
    },
    "cacheControl": "string",
    "host": {
      "hostString": "string",
      "address": {
        "hostAddress": "string",
        "address": [
          "string"
        ],
        "hostName": "string",
        "linkLocalAddress": true,
        "multicastAddress": true,
        "anyLocalAddress": true,
        "loopbackAddress": true,
        "siteLocalAddress": true,
        "mcglobal": true,
        "mcnodeLocal": true,
        "mclinkLocal": true,
        "mcsiteLocal": true,
        "mcorgLocal": true,
        "canonicalHostName": "string"
      },
      "port": 0,
      "unresolved": true,
      "hostName": "string"
    },
    "location": "string",
    "contentType": {
      "type": "string",
      "subtype": "string",
      "parameters": {
        "additionalProp1": "string",
        "additionalProp2": "string",
        "additionalProp3": "string"
      },
      "qualityValue": 0,
      "wildcardType": true,
      "wildcardSubtype": true,
      "subtypeSuffix": "string",
      "charset": {
        "registered": true
      },
      "concrete": true
    },
    "contentLength": 0,
    "ifModifiedSince": 0,
    "empty": true,
    "lastModified": 0,
    "date": 0,
    "additionalProp1": [
      "string"
    ],
    "additionalProp2": [
      "string"
    ],
    "additionalProp3": [
      "string"
    ]
  },
  "body": <my-model>,
  "statusCodeValue": 0,
  "statusCode": {
    "is1xxInformational": true,
    "is2xxSuccessful": true,
    "is3xxRedirection": true,
    "is4xxClientError": true,
    "is5xxServerError": true,
    "error": true
  }
}

I would expect the defined configuration to work exactly the same as setting the below properties:

springdoc:
  use-fqn: true

Am I missing something here?

razum90 avatar Apr 15 '24 19:04 razum90