OpenAPI spec for reactive REST service using Quarkus - openapi

The jouney so far
I'm trying to get a reactive REST service up and running, following the official guide, using RESTEasy and JSON-B.
I also added support for OpenAPI for testing the service following this guide.
Both parts work on their own, the service properly returns the hard coded demo data. The Swagger UI shows the available routes and allows to invoke them.
However, it's not as smooth as I liked it to be...
From the simple, non-reactive routes, schemas have been correctly extracted:
Fruit:
type: object
properties:
description:
type: string
name:
type: string
But from the reactive routes, empty schemas have been extracted. For example, introducing
#GET
#Path("/{name}")
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
resulted in the schema:
UniFruit:
type: object
Is there a way to re-use the existing Fruit schema?
I tried annotating the route, but that didn't have any effect:
#GET
#Path("/{name}")
// #Schema(ref = "#/components/schemas/Fruit") // Nope...
// #Schema(ref = "Fruit") // Nope...
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}
Ideally, I wouldn't want to annotate every reactive method separately anyways.
The question
Is there a way to configure project-wide to use the schema of T whenever a route returns Uni<T> or Multi<T>?

I have browsed the MicroProfile OpenAPI spec https://github.com/eclipse/microprofile-open-api/blob/master/spec/src/main/asciidoc/microprofile-openapi-spec.adoc but could not find a way to do the project wide schema change you request. You can do it for a given class, but here the class is Uni and you want different schemas depending on the parameterized type.
So the fix in Quarkus seems like the viable approach unless SmallRye OpenAPI (the implementation) has some specific ways to do this.

In the meantime, you can use the implementation attribute of the #Schema annotation:
#GET
#Path("/{name}")
#APIResponse(
content = #Content(mediaType = MediaType.APPLICATION_JSON,
schema = #Schema(implementation = Fruit.class)))
public Uni<Fruit> getOne(#PathParam(value = "name") String name) {
}

Related

Renaming an XML/SOAP tag using Apache CXF

I've got a SOAP web-service server using Apache CXF as implementation. Due to some external technical constraint I'd like to be able to rename some XML tags naming an operation parameter (which are deprecated) in the inbound SOAP request. I was reading about using Interceptors for this, but the documentation on how to setup/configure them is not very clear.
My code to publish an endpoint is the following:
Endpoint endpoint = Endpoint.create(
"http://schemas.xmlsoap.org/soap/", new MyServer());
endpoint.publish("ws/endpoint");
Ideally I'd like to add a filter only to a given endpoint (I have several of them).
Apache's documentations about interceptors are quite clear (IMO), anyway, there is a helloworld project (based on spring boot, cxf and maven) in my github profile which you can take a look for setting up interceptors (in fact it's a baisc autentication interceptor).
For setting up an interceptor (e.g InInterceptor), your class should extend AbstractPhaseInterceptor<Message> and override handleMessage(Message message) method, then in the constructor you should declare the phase in which the interceptor is going to be applied. Finally you have to instantiate it and apply in on an Endpoint.
As you said:
rename some XML tags naming an operation parameter (which are
deprecated) in the inbound SOAP request
I think the name of the operation parameter (in WSDL file) is something different from the argument of your web method. Suppose that there is method in your endpoint named addPerson:
#WebMethod
String addPerson(Person person) {
/*method logic*/
}
and Person class:
class Person {
private String firstName;
private String lastName;
private Date birthDate;
//getters and setters
}
in order to map lastName property to a different name, you have to annotate it with
#XmlElement(name = "sureName")
private String lastName;
after applying this anotation, sureName (in wsdl file) is going to be mapped to lastName.
In addition, there is #WebParam annotation which can be used for changing the name of web method arguments:
#WebMethod
String sayHello( #WebParam(name = "sureName") String lastName);
Hope it helps.

How to use DiscoveredResource to traverse to a single entity resource exposed by a RepositoryRestResource

I'm trying to set up a system with multiple applications connecting by use of a discovery server. I can traverse the hal responses to a specific resource, but I'm looking for a solution to get from a collection resource to a single resource and find the data for a specific entity.
In 1 application I have a RepositoryRestResource exposing some object:
#RestRepositoryResource(collectionResourceRel="things", itemResourceRel="thing") public interface ThingRepo extends CrudRepository<Thing,Long> {}
In some other application, I would like to get to a single thing. I have the id (let's say it's 1) and have the relation name of the collection and the single resource.
I would like to use a DiscoveredResource to get a link to this single item resource, or to the collection resource which I can then somehow expand using the ID (which would require a templated resource).
If at all possible I would not like to just add "/1" at the end of the URL.
this is how I currently create a DiscoveredResource to point to the collection resource:
new DiscoveredResource(new DynamicServiceInstanceProvider(discoveryClient, traverson -> traverson.follow("things"));
Should I and is it possible to add a templated link on a collection resource created by a #RepositoryRestResource. Or is there some other trick I am missing?
The solution here is to add a custom method as a #RestResource which exposes a relation with a templates URL you can then follow to.
Repo:
#RestRepositoryResource(collectionResourceRel="things", itemResourceRel="thing") public interface ThingRepo extends CrudRepository<Thing,Long> {
#RestResource(rel = "thing")
Thing findOneById(#Param("id") Long id);
}
Discovery + traverson:
DiscoveredResource resource = new DiscoveredResource(new DynamicServiceInstanceProvider(discoveryClient, traverson -> traverson.follow("things","search","thing"));
Link link = resource.getLink().expand(id);

Spring Cloud Feign Client #RequestParam with List parameter creates a wrong request

I have a Spring Clound Feign Client mapping defined as following
#RequestMapping(method = RequestMethod.GET, value = "/search/findByIdIn")
Resources<MyClass> get(#RequestParam("ids") List<Long> ids);
when I call
feignClient.get(Arrays.asList(1L,2L,3L))
according to what I can see in the debugger, the feign-core library forms the following request:
/search/findByIdIn?ids=1&ids=2&ids=3
instead of expected
/search/findByIdIn?ids=1,2,3
which would be correct for the server Spring Data REST endpoint declared in the same way as my Feign client method.
Thus, because of this issue, the request always returns empty set.
I have seen similar question, but it looks like the Feign client was working as I expect back in 2015.
I am using:
spring-cloud-starter-feign version 1.2.4.RELEASE
feign-httpclient version 9.4.0
feign-core version 9.4.0
Is there a way to correct the behaviour and "marry" the Spring Cloud Feign Client with the Spring Data REST defined endpoints?
I had the same issue with multiple occurence of the parametre instead of the expected comma separated sequence of items. The solution was really simple:
In my feign client I used arrays
feignClient.get(new Long[]{1L,2L,3L})
instead of collection/list:
feignClient.get(Arrays.asList(1L,2L,3L))
In Feign you can annotate your controller with the following
#CollectionFormat(feign.CollectionFormat.CSV) and it will process collections in
the CSV format findByIdIn?ids=1&ids=2&ids=3
Thanks #prola for your answer.
Just to add an explicit example, #CollectionFormat(feign.CollectionFormat.CSV) annotation targets a method; you can't apply globally to your Feign Client interface.
So each method will be similar to:
#RequestMapping(value = ["/objects"], method = [RequestMethod.GET])
#CollectionFormat(feign.CollectionFormat.CSV)
fun findById(
#RequestParam(value = "object.id", required = true) id: String,
#RequestParam(value = "object.fields", required = false) objectFields: List<String> = DEFAULT_FIELDS_LIST,
#RequestParam(value = "format") format: String = FORMAT,
): ResponseEntity<ObjectsDTO>
The result will be
/objects?object.fields=size,weight,location
instead of
/objects?object.fields=size&object.fields=weight&object.fields=location
You can also refer to:
1.16.Feign CollectionFormat support
OpenFeign #542: Support Multiple Collection Formats
I've just battled with this today, and the solution for me was surprisingly simple.
If you use brackets [] for denoting query array:
Resources<MyClass> get(#RequestParam("ids[]") List<Long> ids);
it will create a request that looks like this
/search/findByIdIn?ids[]=1&ids[]=2&ids[]=3
Most server side frameworks will interpret this as an array.
If your server is also in spring then you can pick this up like this
#GetMapping("/search/findByIdIn")
public ResponseEntity findByIdIn(#RequestParam("ids[]") List<Long> ids) { ... }
Just keep in mind that the query has to be encoded, [] gets encoded to %5B%5D.

API versioning in ASP.NET Web API

I have an ASP.NET Web API I wrote and have published. Now that its out there we are looking at doing some improvements, and these improvements involve changes to certain calls which means we need to version to keep existing clients working.
I have used attribute routing so far in my app. Methods are invoked by: Controller/Action via RoutePrefix and Route attributes.
When I do need to create a V2 of my classes, I only want to recreate the classes that have actually changed, and redirect other routes back to v1 classes because they haven't changed. (Otherwise I just end up with a lot of boilerplate code, or duplicate code).
What I want to do is have the following routes work for my v1 version of classes:
Controller/Action
For V2 I want any new classes to go to V2, and any classes that haven't changed I want to return the HttpControllerDescriptor from V1 class. The route would look like v2/Controller/Action but would be redirected to Controller/Action.
I've implemented a IHttpControllerSelector and return the appropriate HttpControllerDescriptors but its not making the call into the method. I believe its because the routing information doesn't match the action. (When I put in an IHttpActionSelector and trace the exception it says "multiple actions were found that match the request).
So, I'm guess I'm wondering: Is this even possible? Is this the best way to achieve what I'm trying to do?
Here is what I implemented for versioning support in asp.net web api. Important to note I did not use attribute routing but explicit routes in WebApiConfig.cs so if you want to follow this pattern you would need to switch back to explicit routes. Also I do not prefer version information in the actual route, I use a custom (ie. "version") parameter in Accept header. I also set the version per mime type as in the below example. If version number is not set by the client or if the requested version does not exist this will fall back to default controller.
Create a class and inherit from DefaultHttpControllerSelector so you can fallback to base class behavior when you wanted to.
Override SelectController method as such:
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IDictionary controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
string controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor controllerDescriptor;
if (string.IsNullOrWhiteSpace(controllerName))
{
return base.SelectController(request);
}
if (!controllers.TryGetValue(controllerName, out controllerDescriptor))
{
return null;
}
string version = GetVersionFromAcceptHeader(request);
if (string.Equals(version, "1"))
{
return controllerDescriptor;
}
string newName = string.Concat(controllerName, "V", version);
HttpControllerDescriptor versionedControllerDescriptor;
if (controllers.TryGetValue(newName, out versionedControllerDescriptor))
{
return versionedControllerDescriptor;
}
return controllerDescriptor;
}
Register this controller selector in your webapiconfig Register method:
config.Services.Replace(typeof(IHttpControllerSelector), new YourControllerSelector(config));

spring cloud programmatic metadata generation

Is there anyway that I can generate some metadata to add to the service when it registers.
We are moving from Eureka to Consul and I need to add a UUID value to the registered metadata when a service starts. So that later I can get this metadata value when I retrieve the service instances by name.
Some background: We were using this excellent front end UI from https://github.com/VanRoy/spring-cloud-dashboard. It is set to use the Eureka model for services in which you have an Application with a name. Each application will have multiple instances each with an instance id.
So with the eureka model there is a 2 level service description whereas the spring cloud model is a flat one where n instances each of which have a service id.
The flat model won't work with the UI that I referenced above since there is no distinction between application name and instance id which is the spring model these are the same.
So if I generate my own instance id and handle it through metadata then I can preserve some of the behaviour without rewriting the ui.
See the documentation on metadata and tags in spring cloud consul. Consul doesn't support metadata on service discovery yet, but spring cloud has a metadata abstraction (just a map of strings). In consul tags created with key=value style are parsed into that metadata map.
For example in, application.yml:
spring:
cloud:
consul:
discovery:
tags: foo=bar, baz
The above configuration will result in a map with foo→bar and baz→baz.
Based on Spencer's answer I added an EnvironmentPostProcessor to my code.
It works and I am able to add the metadata tag I want programmatically but it is a complement to the "tags: foo=bar, baz" element so it overrides that one. I will probably figure a way around it in the next day or so but I thougth I would add what I did for other who look at this answer and say, so what did you do?
first add a class as follows:
#Slf4j
public class MetaDataEnvProcessor implements EnvironmentPostProcessor, Ordered {
// Before ConfigFileApplicationListener
private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;
private UUID instanceId = UUID.randomUUID();
#Override
public int getOrder() {
return this.order;
}
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
map.put("spring.cloud.consul.discovery.tags", "instanceId="+instanceId.toString());
MapPropertySource propertySource = new MapPropertySource("springCloudConsulTags", map);
environment.getPropertySources().addLast(propertySource);
}
}
then add a spring.factories in resources/META-INF with eht following line to add this processor
org.springframework.boot.env.EnvironmentPostProcessor=com.example.consul.MetaDataEnvProcessor
This works fine except for the override of what is in your application.yml file for tags