Skip to content

Schema replaced by String when using @ApiResponse with RepresentationModel (Hateoas links) #2902

Closed
@didjoman

Description

@didjoman

Describe the bug

When using the annotation @ApiResponse with a content and a mediatype, the schema of my response object is being replaced by a String (instead of the reference to the actual schema).

Example:

    @GetMapping("/test")
    @ResponseStatus(HttpStatus.OK)
    @Operation(summary = "get", description = "Provides a response.")
    @ApiResponse(content = @Content(mediaType = MediaTypes.HAL_JSON_VALUE,
        schema = @io.swagger.v3.oas.annotations.media.Schema(implementation = Response.class)),
        responseCode = "200")
    public Response get() {
        return new Response("value");
    }

This is due to a NullPointerException occuring in the new HateoasLinksConverter.
The stacktrace (not displayed as catched in SpringDocAnnotationsUtils line 442) :

        java.lang.NullPointerException: Cannot invoke "String.substring(int)" because the return value of "io.swagger.v3.oas.models.media.Schema.get$ref()" is null
	at org.springdoc.core.converters.HateoasLinksConverter.resolve(HateoasLinksConverter.java:74)
	at org.springdoc.core.converters.PageableOpenAPIConverter.resolve(PageableOpenAPIConverter.java:97)
	at org.springdoc.core.converters.PageOpenAPIConverter.resolve(PageOpenAPIConverter.java:102)
	at org.springdoc.core.converters.SortOpenAPIConverter.resolve(SortOpenAPIConverter.java:92)
	at io.swagger.v3.core.converter.ModelConverterContextImpl.resolve(ModelConverterContextImpl.java:97)
	at io.swagger.v3.core.jackson.ModelResolver.resolve(ModelResolver.java:493)
	at org.springdoc.core.converters.WebFluxSupportConverter.resolve(WebFluxSupportConverter.java:89)
	at org.springdoc.core.converters.AdditionalModelsConverter.resolve(AdditionalModelsConverter.java:163)
	at org.springdoc.core.converters.FileSupportConverter.resolve(FileSupportConverter.java:72)
	at org.springdoc.core.converters.ResponseSupportConverter.resolve(ResponseSupportConverter.java:84)
	at org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.resolve(SchemaPropertyDeprecatingConverter.java:95)
	at org.springdoc.core.converters.PolymorphicModelConverter.resolve(PolymorphicModelConverter.java:141)
	at org.springdoc.core.converters.OAS31ModelConverter.resolve(OAS31ModelConverter.java:49)
	at org.springdoc.core.converters.CollectionModelContentConverter.resolve(CollectionModelContentConverter.java:84)
	at org.springdoc.core.converters.HateoasLinksConverter.resolve(HateoasLinksConverter.java:87)
	at org.springdoc.core.converters.PageableOpenAPIConverter.resolve(PageableOpenAPIConverter.java:97)
	at org.springdoc.core.converters.PageOpenAPIConverter.resolve(PageOpenAPIConverter.java:102)
	at org.springdoc.core.converters.SortOpenAPIConverter.resolve(SortOpenAPIConverter.java:92)
	at io.swagger.v3.core.converter.ModelConverterContextImpl.resolve(ModelConverterContextImpl.java:97)
	at io.swagger.v3.core.jackson.ModelResolver.resolve(ModelResolver.java:745)
	at org.springdoc.core.converters.WebFluxSupportConverter.resolve(WebFluxSupportConverter.java:89)
	at org.springdoc.core.converters.AdditionalModelsConverter.resolve(AdditionalModelsConverter.java:163)
	at org.springdoc.core.converters.FileSupportConverter.resolve(FileSupportConverter.java:72)
	at org.springdoc.core.converters.ResponseSupportConverter.resolve(ResponseSupportConverter.java:84)
	at org.springdoc.core.converters.SchemaPropertyDeprecatingConverter.resolve(SchemaPropertyDeprecatingConverter.java:95)
	at org.springdoc.core.converters.PolymorphicModelConverter.resolve(PolymorphicModelConverter.java:141)
	at org.springdoc.core.converters.OAS31ModelConverter.resolve(OAS31ModelConverter.java:49)
	at org.springdoc.core.converters.CollectionModelContentConverter.resolve(CollectionModelContentConverter.java:84)
	at org.springdoc.core.converters.HateoasLinksConverter.resolve(HateoasLinksConverter.java:73)
	at org.springdoc.core.converters.PageableOpenAPIConverter.resolve(PageableOpenAPIConverter.java:97)
	at org.springdoc.core.converters.PageOpenAPIConverter.resolve(PageOpenAPIConverter.java:102)
	at org.springdoc.core.converters.SortOpenAPIConverter.resolve(SortOpenAPIConverter.java:92)
	at io.swagger.v3.core.converter.ModelConverterContextImpl.resolve(ModelConverterContextImpl.java:97)
	at io.swagger.v3.core.converter.ModelConverters.resolveAsResolvedSchema(ModelConverters.java:192)
	at org.springdoc.core.utils.SpringDocAnnotationsUtils.extractSchema(SpringDocAnnotationsUtils.java:137)
	at org.springdoc.core.service.GenericResponseService.calculateSchema(GenericResponseService.java:563)
	at org.springdoc.core.service.GenericResponseService.buildContent(GenericResponseService.java:541)
	at org.springdoc.core.service.GenericResponseService.buildContent(GenericResponseService.java:522)
	at org.springdoc.core.service.GenericResponseService.buildApiResponses(GenericResponseService.java:595)
	at org.springdoc.core.service.GenericResponseService.buildApiResponses(GenericResponseService.java:469)
	at org.springdoc.core.service.GenericResponseService.build(GenericResponseService.java:264)
	at org.springdoc.api.AbstractOpenApiResource.calculatePath(AbstractOpenApiResource.java:626)
	at org.springdoc.api.AbstractOpenApiResource.calculatePath(AbstractOpenApiResource.java:816)
	at org.springdoc.webmvc.api.OpenApiResource.lambda$calculatePath$11(OpenApiResource.java:222)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at org.springdoc.webmvc.api.OpenApiResource.calculatePath(OpenApiResource.java:203)
	at org.springdoc.webmvc.api.OpenApiResource.lambda$getPaths$2(OpenApiResource.java:173)
	at java.base/java.util.Optional.ifPresent(Optional.java:178)
	at org.springdoc.webmvc.api.OpenApiResource.getPaths(OpenApiResource.java:152)
	at org.springdoc.api.AbstractOpenApiResource.getOpenApi(AbstractOpenApiResource.java:370)
	at org.springdoc.webmvc.api.OpenApiResource.openapiJson(OpenApiResource.java:127)
	at org.springdoc.webmvc.api.OpenApiWebMvcResource.openapiJson(OpenApiWebMvcResource.java:117)

How to reproduce

git clone https://github.com/didjoman/Springdoc-String-Schema-returned-mediatype-issue
Run the application (spring boot).
Run curl --location --request GET 'http://localhost:8080/api-docs' --header 'Content-Type: application/json'

Expected behavior

I expect to have the same OAS generated as before v2.8.2, that is to say:

{
  "openapi": "3.1.0",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/test": {
      "get": {
        "tags": [
          "basic-controller"
        ],
        "summary": "get",
        "description": "Provides a response.",
        "operationId": "get",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/hal+json": {
                "schema": {
                  "$ref": "#/components/schemas/Response"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Link": {
        "type": "object",
        "properties": {
          "href": {
            "type": "string"
          },
          "hreflang": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "deprecation": {
            "type": "string"
          },
          "profile": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "templated": {
            "type": "boolean"
          }
        }
      },
      "Response": {
        "type": "object",
        "description": "test description",
        "properties": {
          "value2": {
            "type": "string"
          },
          "_links": {
            "$ref": "#/components/schemas/Links"
          }
        }
      },
      "Links": {
        "type": "object",
        "additionalProperties": {
          "$ref": "#/components/schemas/Link"
        }
      }
    }
  }
}

Actual behavior

Since v2.8.2 I have this, the Response schema is not generated and its reference in the path is replaced by the type String:

{
  "openapi": "3.1.0",
  "info": {
    "title": "OpenAPI definition",
    "version": "v0"
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Generated server url"
    }
  ],
  "paths": {
    "/test": {
      "get": {
        "tags": [
          "basic-controller"
        ],
        "summary": "get",
        "description": "Provides a response.",
        "operationId": "get",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/hal+json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Link": {
        "type": "object",
        "properties": {
          "href": {
            "type": "string"
          },
          "hreflang": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "deprecation": {
            "type": "string"
          },
          "profile": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "templated": {
            "type": "boolean"
          }
        }
      },
      "Links": {
        "type": "object",
        "additionalProperties": {
          "$ref": "#/components/schemas/Link"
        }
      }
    }
  }
}

Additional context

The issue did not occur before v2.8.2, and it is still present in v2.8.4.
I think it may be related to this commit 88f5da0 introducing the HateoasLinkConverter. But it's not clear what should be done to fix it.

Maybe, the swagger-core.ModelResolver should be called with a new AnnotatedType(returnType) .resolveAsRef(true).jsonViewAnnotation(jsonView).ctxAnnotations(annotations) as it is done in SpringDocAnnotationsUtils.extractSchema() line 138 ? Because, here it gets called with resolveAsRef(false), then the ref is not added to the schema at line ModelResolver.resolve() line 1060 and then it breaks in HateoasLinksConverter.resolve() line 74.

Thank you for your help :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions