Skip to content

Commit

Permalink
Schema replaced by String when using @ApiResponse with Representation…
Browse files Browse the repository at this point in the history
…Model (Hateoas links). Fixes #2902
  • Loading branch information
bnasslahsen committed Feb 16, 2025
1 parent cdfaf63 commit a329910
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@


import java.util.Iterator;
import java.util.Optional;

import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.ModelConverter;
Expand All @@ -43,7 +44,7 @@

/**
* The type Hateoas links converter.
*
*
* @author bnasslahsen
*/
public class HateoasLinksConverter implements ModelConverter {
Expand All @@ -70,19 +71,30 @@ public Schema<?> resolve(
) {
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
if (javaType != null && RepresentationModel.class.isAssignableFrom(javaType.getRawClass())) {
Schema<?> schema = chain.next().resolve(type, context, chain);
String schemaName = schema.get$ref().substring(Components.COMPONENTS_SCHEMAS_REF.length());
Schema original = context.getDefinedModels().get(schemaName);
Object links = original.getProperties().get("_links");
if(links instanceof JsonSchema jsonSchema) {
jsonSchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
jsonSchema.setType(null);
jsonSchema.setItems(null);
jsonSchema.setTypes(null);
} else if (links instanceof ArraySchema arraySchema){
arraySchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
Schema<?> schema = chain.next().resolve(type, context, chain);
if (schema != null) {
String schemaName = Optional.ofNullable(schema.get$ref())
.filter(ref -> ref.startsWith(Components.COMPONENTS_SCHEMAS_REF))
.map(ref -> ref.substring(Components.COMPONENTS_SCHEMAS_REF.length()))
.orElse(schema.getName());
if(schemaName != null) {
Schema original = context.getDefinedModels().get(schemaName);
if (original == null || original.getProperties() == null) {
return schema;
}
Object links = original.getProperties().get("_links");
if (links instanceof JsonSchema jsonSchema) {
jsonSchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
jsonSchema.setType(null);
jsonSchema.setItems(null);
jsonSchema.setTypes(null);
}
else if (links instanceof ArraySchema arraySchema) {
arraySchema.set$ref(AnnotationsUtils.COMPONENTS_REF + "Links");
}
}
return schema;
}
return schema;
}
return chain.hasNext() ? chain.next().resolve(type, context, chain) : null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
*
* *
* * *
* * * *
* * * * *
* * * * * * Copyright 2019-2025 the original author or authors.
* * * * * *
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
* * * * * * you may not use this file except in compliance with the License.
* * * * * * You may obtain a copy of the License at
* * * * * *
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
* * * * * *
* * * * * * Unless required by applicable law or agreed to in writing, software
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * * * * * See the License for the specific language governing permissions and
* * * * * * limitations under the License.
* * * * *
* * * *
* * *
* *
*
*/

package test.org.springdoc.api.v31.app11;

import test.org.springdoc.api.v31.AbstractSpringDocTest;

import org.springframework.boot.autoconfigure.SpringBootApplication;

public class SpringDocApp11Test extends AbstractSpringDocTest {

@SpringBootApplication
static class SpringDocTestApp {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package test.org.springdoc.api.v31.app11.configuration;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

@Configuration
public class WebMvcConfiguration {

@Bean
MappingJackson2HttpMessageConverter getMappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON));
converter.setObjectMapper(new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)
);

return converter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package test.org.springdoc.api.v31.app11.controllers;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import test.org.springdoc.api.v31.app11.model.Cat;

import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path = "/")
public class BasicController {

@GetMapping("/cat")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "get", description = "Provides an animal.")
public String get(Cat cat) {
return cat != null ? cat.getName() : "";
}

@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");
}

// dummy
public static class Response extends RepresentationModel {
public Response(String v) {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package test.org.springdoc.api.v31.app11.controllers;

import org.springdoc.core.customizers.SpringDocCustomizers;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.SpringDocProviders;
import org.springdoc.core.service.AbstractRequestService;
import org.springdoc.core.service.GenericResponseService;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.OperationService;
import org.springdoc.webmvc.api.OpenApiWebMvcResource;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomOpenApiWebMvcResource extends OpenApiWebMvcResource {

public CustomOpenApiWebMvcResource(ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory,
AbstractRequestService requestBuilder,
GenericResponseService responseBuilder,
OperationService operationParser,
SpringDocConfigProperties springDocConfigProperties,
SpringDocProviders springDocProviders,
SpringDocCustomizers springDocCustomizers) {
super(openAPIBuilderObjectFactory, requestBuilder, responseBuilder, operationParser, springDocConfigProperties, springDocProviders, springDocCustomizers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package test.org.springdoc.api.v31.app11.model;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "Represents a Cat class.")
public class Cat {

@JsonUnwrapped
@Schema(description = "The name.", nullable = true)
private String name;

public Cat(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
{
"openapi": "3.1.0",
"info": {
"title": "OpenAPI definition",
"version": "v0"
},
"servers": [
{
"url": "http://localhost",
"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"
}
}
}
}
}
}
},
"/cat": {
"get": {
"tags": [
"basic-controller"
],
"summary": "get",
"description": "Provides an animal.",
"operationId": "get_1",
"parameters": [
{
"name": "cat",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/Cat"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Response": {
"type": "object",
"properties": {
"_links": {
"$ref": "#/components/schemas/Links"
}
}
},
"Cat": {
"type": "object",
"description": "Represents a Cat class.",
"properties": {
"name": {
"type": "string",
"description": "The name."
}
}
},
"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"
}
}
}
}
}

0 comments on commit a329910

Please # to comment.