How to use ref of schema as oneOf/anyOf responses of an endpoint using springdoc-openapi? - spring

I'm using springdoc-openapi and I defined a few schemas using one of my classes (so they don't have individual classes). I've defined them using addSchemas in OpenAPI bean as mentioned in this issue:
https://github.com/springdoc/springdoc-openapi/issues/685#issuecomment-636028451
It's something like this:
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI().components(new Components()
.addSchemas("ErrorOne", getSchemaWithDifferentDescription(ErrorObject.class, "ErrorOne")
.addSchemas("ErrorTwo", getSchemaWithDifferentDescription(ErrorObject.class, "ErrorTwo")));
}
private Schema getSchemaWithDifferentDescription(Class className, String description){
ResolvedSchema resolvedSchema = ModelConverters.getInstance()
.resolveAsResolvedSchema(
new AnnotatedType(className).resolveAsRef(false));
return resolvedSchema.schema.description(description);
}
Now I want to use these schemas in oneOf (or anyOf) using ref of them as possible responses of an endpoint. Something like this:
#PostMapping
#ApiResponses({
#ApiResponse(responseCode = "400",
content = { #Content(mediaType = "application/json",
schema = #Schema(oneOf = {"ErrorOne", "ErrorTwo"})) }
)
})
public HttpResponse myEndpoint(#RequestBody HttpRequest httpRequest) {
...
}
But the problem is that oneOf field in #Schema annotation only accepts a list of classes, and I can not use the ref of those schemas here.
Is there a way to link these schemas to oneOf/anyOf using their references in spring-doc?
And also I wanted to know is there a way to add some global responses as oneOf/anyOf responses for my endpoints (beside their own possible responses)?

Related

How to write #ApiResponse which may return a Class or a List of that class using OpenAPI 3 Swagger in Spring Boot

As written in documentation we can use anyOf with #Schema if we want to define multiple responses.
#ApiResponse(responseCode = "201", description = "OK",
content = #Content(schema = #Schema(anyOf = {Product.class, Activity.class})))
My controller returns either a Product or a List<Product>. I would like to specify this in my OpenAPI 3 documentation.
I would like to know if it's possible.
If Yes, then how?
If No, then is there any workaround?
I don't only want to specify List.class. I want to specify List<Product>.
P.S.:- Searching on Google didn't get me any results that I can use.
Ok, thats a tough one.
Basically if you really want to return a List of Objects or one Object, then you can create a basic interface like this
public interface Response {
}
And then you can create your Object, which implements the response
public class Hello implements Response {
private String message;
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
}
Finally we can create the List of our Object. For that we need to create a class, which extends the ArrayList and implements our Interface
public class HelloList extends ArrayList<Hello> implements Response {
}
After that we can just set our schema as implementation
#ApiResponse(responseCode = "200", description = "hello world", content = #Content(mediaType = "application/json", schema = #Schema(implementation = Response.class)))
On the Clientside you need to check the instance of the Response-Object, so that you can parse either the Object or the List
Response response = someCall();
if (response instanceof Hello) {
System.out.println(processHello((Hello) response);
}
if (response instanceof HelloList) {
System.out.println(processHelloList((HelloList) response);
}
This example works, but its very very complex und confusing. A better way to design your api, would be to return just the list. I don't see the benefit to seperate one object or the list of it.

Populate query parameters from DTO

Is there a way to let Spring populate RestTemplate query parameters automatically from a DTO, similarly to how it instantiates the response DTO automatically?
I wish to write something like:
RequestDto request = new RequestDto();
request.setFoo("foo");
request.setBar("bar");
ResponseDto response = restTemplate.getForObject(
"http://example.com/api",
ResponseDto.class,
request
);
Instead of:
ResponseDto response = restTemplate.getForObject(
"http://example.com/api?foo={foo}&bar={bar}",
ResponseDto.class,
"foo",
"bar"
);
Because there are many large DTOs, requiring tons of boilerplate code, which must be kept in sync with any DTO changes.
Spring 4.3.25
I don't think that is directly possible. The following is not exactly using the DTO, but it does let you build the request without having to form the URL string manually. You can use Spring's UriComponentsBuilder class.
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api")
.queryParam("foo", "bar")
// etc. ...
.queryParam("bar", "foo");
String result = restTemplate.getForObject(builder.toString(), String.class);
You could loop over the DTO and build the query as above. Or without the DTO, you could use a Map<String, String> and loop over it.
Map<String, String> params = new HashMap<>();
params.put("foo", "bar");
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api");
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
String result = restTemplate.getForObject(builder.toString(), String.class);
Edit:
As crizzis suggested below, you can use Spring Cloud OpenFeign's REST Client (from Feign #QueryMap support):
The OpenFeign #QueryMap annotation provides support for POJOs to be used as GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is incompatible with Spring because it lacks a value property.
and
Spring Cloud OpenFeign provides an equivalent #SpringQueryMap annotation, which is used to annotate a POJO or Map parameter as a query parameter map.
From your question's example:
public class RequestDto {
private string foo;
private string bar;
}
#FeignClient(name = "client", url = "http://example.com")
public interface FooTemplate {
#GetMapping(path = "/api")
String endpoint(#SpringQueryMap RequestDto requestDto);
}
You can do something like this-
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/api")
.queryParam("foo", "foo")
.queryParam("bar", "bar");
ResponseDto response = restTemplate.getForObject(
builder.buildAndExpand(builder).toUriString(),
ResponseDto.class);
A more detailed answer can be found here- RestTemplate: How to send URL and query parameters together
How about using Feign? It allows you to describe the remote endpoint just like a Spring Controller. This includes support for query parameter DTOs.
See an example here

Spring multiple entity JSON serializers

I am looking for a way to create multiple json serializers for my entity. I have created service layer, custom serilizers and now I have problem with implementation of this things.
My Service class looks like:
#Service
class TeamsService(#Autowired private val teamsRepository: TeamsRepository) : ITeamsService{
override fun findAll(): String? {
var objectMapper = ObjectMapper()
var simpleModule = SimpleModule()
simpleModule.addSerializer(Teams::class.java, TeamsSerializer())
objectMapper.registerModule(simpleModule)
return objectMapper.writeValueAsString(teamsRepository.findAll())
}
}
And my Controller looks like:
#RestController
#RequestMapping("/v1")
class MainController(#Autowired private val teamsService: TeamsService) {
#GetMapping("/teams")
fun teams(): String? = teamsService.findAll()
}
Now I have problem that my response lost all headers and appears as text/plain not text/json, like it was before I added custom mapper.
I Was reading about projections but I am not sure if I should use them. I do not want to have query parameters in my url.
I found solution. The best way to do this is to use ModelMapper library. You can simply map entity to custom classes and serialize

How to accept RequestBody of different class types dynamically

I am using Spring Boot . Writing rest api's
where for the same api url , the request json structure varies
Is there any way we can apply Factory design or some thing else
#RequestMapping(value = "/myservice/{type}", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> myServiceApi(#PathVariable String type,
#RequestBody SomeClass1 somereq) {
// here based on type , the RequestBody can be either SomeClass1 or SomeClass2
// both the SomeClass1 and SomeClass2 has nothing in common .
}
The above code will work only if the request json is in SomeClass1 format , but i needed it to accept among {SomeClass1 , SomeClass2}
You could do this by passing the JSON as a String into your controller method and then mapping this to whichever object you expect to need:
#PostMapping(value = "/myservice/{type}")
public ResponseEntity<?> myServiceApi(#PathVariable String type,
#RequestBody String somereq) {
ObjectMapper mapper = new ObjectMapper();
if (<something that indicates SomeClass1>) {
SomeClass1 someClass1 = mapper.readValue(somereq, SomeClass1.class);
} else if (<something that indicates SomeClass2>) {
SomeClass2 someClass2 = mapper.readValue(somereq, SomeClass2.class);
}
}
Although to be honest if you really are expecting bodies with completely different structures my advice would be to just make separate API calls for these.

Spring web service maven-jaxb2-plugin complex input

I need to create a client based on WSDL. I am using Spring 4 and Spring web service 2.2.0. Using maven-jaxb2-plugin I created classes and able to invoke web service. I am using object factory to create some objects, due to complex data type. Now I have a complex input. and I don't find appropriate methods in the generated class.
As per SOAP UI , the request body looks like
<soapenv:Body>
<zhin:ZHIN_HR_CA_EVALUATOR_SEND_RFCresponse>
<IN_DATA>
<IV_PERNR>4005002</IV_PERNR>
<IT_RATERS>
<!--1 or more repetitions:-->
<ArrayOfIT_RATERSItem>
<ORGTX>?</ORGTX>
<DESIG>?</DESIG>
<ENAME>N V S Ravi Kumar</ENAME>
<ZCPERNRT>?</ZCPERNRT>
<PERNR>4005001</PERNR>
<WEIGH>?</WEIGH>
<SEQUENCE>1St Evaluator</SEQUENCE>
</ArrayOfIT_RATERSItem>
</IT_RATERS>
<IV_APCAT>1</IV_APCAT>
</IN_DATA>
</zhin:ZHIN_HR_CA_EVALUATOR_SEND_RFCresponse>
</soapenv:Body>
Now i don't find a method to set ArrayOfITRATERS but I have getArrayOfITRATERSItem()
final ObjectFactory objectFactory = new ObjectFactory();
final INDATA requestValue = objectFactory.createINDATA();
requestValue.setIVPERNR(String.valueOf(id));
requestValue.setIVAPCAT(AppConstants.CA);
final ArrayOfITRATERS value = objectFactory.createArrayOfITRATERS();
value.set .....(not found ???)
requestValue.setITRATERS(value);
My autogenerated ArrayOfITRATERS class looks like
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "ArrayOfIT_RATERS", propOrder = {
"arrayOfITRATERSItem"
})
public class ArrayOfITRATERS {
#XmlElement(name = "ArrayOfIT_RATERSItem", required = true)
protected List<ITRATERS> arrayOfITRATERSItem;
public List<ITRATERS> getArrayOfITRATERSItem() {
if (arrayOfITRATERSItem == null) {
arrayOfITRATERSItem = new ArrayList<ITRATERS>();
}
return this.arrayOfITRATERSItem;
}
}
I am not sure how to set the ArrayOfITRATERS
Any help is appreciated.
Does
getArrayOfITRATERSItem().add(myITRATERS);
solve your problem?
JAXB XJC does not generate setters for collections by default. Just add your items to the collection returned by the getter.

Resources