Swagger responseContainer = "List" not working - jackson

Below is my interface
#Api(value = "/abc/def", basePath = "/abc/def", description = "")
public interface test{
#ApiOperation(value = "some description" , response = SomeDTO.class ,responseContainer = "List" )
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful response"),
})
#GET
#Path("/{Id}")
List<SomeDTO> getById(#PathParam("Id") String Id);
}
In the Swagger UI when i provide the Id and Click on Try out it doesn't display the list of results . It just hangs.
Below are my Dependencies
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.3.10</version>
</dependency>
SomeDTO Looks like below in Swagger UI
SomeDTO {
Id (integer, optional),
TestType (TestType, optional) = ['QA' or 'PROD'], // enum in java class
Time (string, optional),
Status (Status, optional) = ['Y' or 'N'], // enum in java class
}
SomeDTO.java
public class SomeDTO {
private Integer Id;
private TestType testType;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
private Date Time;
private Status status;
// getters and setters
}
I am able to get the results by Chrome Poster Plugin .
Can someone please let me know what i have missed while configuring swagger to return a list of SomeDTO types..

Related

Formatting YAML with Jackson

I am using the Jackson library to convert Java objects to YAML format. Based on the documentation I found on the Internet, I was able to quickly write a function that does the conversion.
I am seeking to convert the following classes to YAML:
public class RequestInfo
{
private String thePath;
private String theMethod;
private String theURL;
private List<ParamInfo> theParams = new ArrayList<>();
// getters and setters
}
public class ParamInfo
{
private String paramName;
private String paramType;
// getters and setters
}
Using Jackson's ObjectMapper, I can easily generate the YAML:
public String basicTest()
{
ObjectMapper theMapper = new ObjectMapper(new YAMLFactory());
RequestInfo info = new RequestInfo();
info.setThePath("/");
info.setTheMethod("GET");
info.setTheURL("http://localhost:8080/");
List<ParamInfo> params = new ArrayList<>();
params.add(new ParamInfo("resource","path"));
info.setTheParams(params);
String ret = null;
try
{
ret = theMapper.writeValueAsString(info);
}
catch(Exception exe)
{
logger.error(exe.getMessage());
}
return(ret);
}
The YAML I get is below:
---
thePath: "/"
theMethod: "GET"
theURL: "http://localhost:8080/"
theParams:
- paramName: "resource"
paramType: "path"
The YAML I get is OK, but it has some problems in my eyes. One probem is the "---" that it begins with. Another is the fact that I would like to be able to group the information in a manner similar to the YAML below:
RequestInfo:
thePath: "/"
theMethod: "GET"
theURL: "http://localhost:8080/"
theParams:
- paramName: "resource"
paramType: "path"
All of the examples I am seeing on the Internet use an Employee class, and talk about how to convert that class to YAML, but do not tell how to avoid the "---" (or change it into soething more descriptive). I also cannot find anything that tells how to group the YAML in the manner I describe.
Does anyone know how to do this? Is there a way to eliminate the "---", or create a name (like "RequestInfo") that groups together the translated data in an object?
You can ignore --- by disable YAMLGenerator.Feature.WRITE_DOC_START_MARKER..
If you want to wrap value under class name then u need to use #JsonRootName...
Try with this:
RequestInof class:
#JsonRootName("RequestInfo")
public class RequestInfo
{
private String thePath;
private String theMethod;
private String theURL;
private List<ParamInfo> theParams = new ArrayList<>();
// getters and setters
}
Test:
public String basicTest()
{
ObjectMapper theMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
theMapper.enable(SerializationFeature.WRAP_ROOT_VALUE); RequestInfo info = new RequestInfo();
info.setThePath("/");
info.setTheMethod("GET");
info.setTheURL("http://localhost:8080/");
List<ParamInfo> params = new ArrayList<>();
params.add(new ParamInfo("resource","path"));
info.setTheParams(params);
String ret = null;
try
{
ret = theMapper.writeValueAsString(info);
}
catch(Exception exe)
{
logger.error(exe.getMessage());
}
return(ret);
}

How to set the OpenAPI type/schema for a property in a Kotlin data class

In a Microprofile / Quarkus project using Kotlin have a data class with a variable of type Instant.
#Schema(name = "Vehicle", description = "POJO that represents a vehicle at a specific time.")
data class VehicleDTO(
var time: Instant = Instant.EPOCH
)
The problem is that the generated openapi schema does not represent how the value of Instant is actually transmitted.
The schema looks like the following, whereas it is simply represented as a string like that: 2015-06-02T21:34:33.616Z.
Instant:
type: object
properties:
nanos:
format: int32
type: integer
seconds:
format: int64
type: integer
epochSecond:
format: int64
type: integer
nano:
format: int32
type: integer
I already tried to annotate the data class to use the implementation string and type string, but it does not change anything.
#Schema(name = "Vehicle", description = "POJO that represents a vehicle at a specific time.")
data class VehicleDTO(
#Schema(implementation = String::class, type = SchemaType.STRING)
var time: Instant = Instant.EPOCH
)
The issue is that data classes receive a bit of special treatment and your annotation is placed on the constructor argument.
You can see this in the generated Java code of your data class. Relevant snippet:
#Schema(
name = "Vehicle",
description = "POJO that represents a vehicle at a specific time."
)
#Metadata(...)
public final class VehicleDTO {
#NotNull
private Instant time;
#NotNull
public final Instant getTime() {
return this.time;
}
public final void setTime(#NotNull Instant var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.time = var1;
}
public VehicleDTO(#Schema(implementation = String.class,type = SchemaType.STRING) #NotNull Instant time) {
Intrinsics.checkParameterIsNotNull(time, "time");
super();
this.time = time;
}
// ...
}
You need to tell Kotlin to place it on the field using a use-site target:
#Schema(name = "Vehicle", description = "POJO that represents a vehicle at a specific time.")
data class VehicleDTO(
#field:Schema(implementation = String::class, type = SchemaType.STRING)
var time: Instant = Instant.EPOCH
)
Relevant code afterwards:
#Schema(
name = "Vehicle",
description = "POJO that represents a vehicle at a specific time."
)
#Metadata(...)
public final class VehicleDTO {
#Schema(
implementation = String.class,
type = SchemaType.STRING
)
#NotNull
private Instant time;
#NotNull
public final Instant getTime() {
return this.time;
}
public final void setTime(#NotNull Instant var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.time = var1;
}
public VehicleDTO(#NotNull Instant time) {
Intrinsics.checkParameterIsNotNull(time, "time");
super();
this.time = time;
}
// ...
}

Swagger-ui is not showing control documentation

I am trying to use springfox-swagger-ui for the documentation of my rest services. I had used the next basic configuration in a kotlin project:
This is my Docket class:
#Configuration
#EnableSwagger2
open class SwaggerConfig {
#Bean
open fun newsApi(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.groupName("api-infos")
.apiInfo(apiInfo())
.directModelSubstitute(LocalDateTime::class.java, Date::class.java)
.select()
.paths(regex("/api.*"))
.build()
}
private fun apiInfo(): ApiInfo {
return ApiInfoBuilder()
.title("Infos REST api")
.description("Swagger test for Api ESPN")
.termsOfServiceUrl("http://en.wikipedia.org/wiki/Terms_of_service")
.contact("rodolfo.silva#globant.com")
.license("Apache License Version 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.version("1.0")
.build()
}
}
And this is my controller:
#Controller
#ProductApi(
id = "v1_browse_player",
title = "Browse Player (v1)",
description = "")
#Api(value = "controller", description = "Controllers API", produces = "application/json")
#RequestMapping("/api/infos")
class BrowsePlayerController {
#Autowired
lateinit var browsePlayerService: BrowsePlayerServiceRepresentable
#GetRequest(
path = "/v1/browse/players",
timeToLive = 300,
queries = [
QueryParameter(name = "swid", required = true),
QueryParameter(name = "uid"),
QueryParameter(name = "seeAll", type = java.lang.Boolean::class),
QueryParameter(name = "lang", required = true),
QueryParameter(name = "region", required = true),
QueryParameter(name = "version", required = true, type = Integer::class),
QueryParameter(name = "appName", required = true),
QueryParameter(name = "platform", required = true)
]
)
#ApiOperation(value = "Get the players", notes = "Returns one info for playerBrowse")
fun processBrowsePlayerRequest(transaction: Transaction, apiRequest: ApiRequest): Single<BrowsePlayerResponse?> {
val applicationContext = RequestBasedApplicationContext(apiRequest)
val standardContext = RequestBasedStandardContext(
RequestBasedVersionContext(apiRequest),
applicationContext,
RequestBasedEditionContext(apiRequest, applicationContext),
RequestBasedPlatformContext(apiRequest),
transaction
)
val swidContext = RequestBasedSWIDContext(apiRequest)
val uidContext = if (checkUIDPresent(apiRequest)) RequestBasedUIDContext(apiRequest) else null
val seeAllContext = RequestBasedSeeAllContext(apiRequest)
val requestBrowsePlayerContext = RequestBrowsePlayerContext(standardContext, swidContext, uidContext, seeAllContext, apiRequest)
return browsePlayerService.getEntitiesBrowse(requestBrowsePlayerContext)
}
private fun checkUIDPresent(apiRequest: ApiRequest): Boolean =
apiRequest.parameters["uid"] != null
}
I had used a very basic configuration, the ApiOperation, Api and RequestMapping("/api/infos") tags, also at the data class level, the next configuration:
#JsonInclude(JsonInclude.Include.NON_NULL)
data class TopBrowsePlayerHeader(val title: String, val searchURL: String?)
#ApiModel(value = "Info entity", description = "Entity class BrowsePlayerResponse")
data class BrowsePlayerResponse(
#ApiModelProperty(value = "The header of the info", required = false)
val header: TopBrowsePlayerHeader,
#ApiModelProperty(value = "The analytics node of the info", required = true)
val analytics: Analytics,
#ApiModelProperty(value = "The sections node of the info", required = true)
val sections: List<Section>)
When I load the http://localhost:8080/swagger-ui.html#/api-controller (swagger browser). I can't see my controller structure. It seems like there is a predefined endpoint which is shown:
http://localhost:8080/v2/api-docs?group=api-infos
I am not pretty familiar with this configuration. Any ideas to the correct configuration?
Thanks
Try to replace paths value with PathSelectors.any():
#Bean
open fun newsApi() : Docket {
return Docket(DocumentationType.SWAGGER_2)
.groupName("api-infos")
.apiInfo(apiInfo())
.directModelSubstitute(LocalDateTime::class.java, Date::class.java)
.select()
.paths(PathSelectors.any())
.build()
}
The default value for swagger path is /v2/api-docs.
You can change is in the application.properties with the springfox.documentation.swagger.v2.path key to whatever you want to.
?group=api-infos comes from the .groupName("api-infos") value.
If you don't want to group your APIs for any reasons (like sets of selected APIs for specific clients) then remove the .groupName(...).

Feign Client and Spring-data-rest (HAL): Howto navigate to linked (`_links`) resorces?

finally after extensive stack-overflowing ;-) and debugging I made it work:
My Feign-client can make requests on Spring-Data-Rest's API and I get a Resource<Something> with filled links back.
My code so far...
The FeignClient:
#FeignClient(name = "serviceclient-hateoas",
url = "${service.url}",
decode404 = true,
path = "${service.basepath:/api/v1}",
configuration = MyFeignHateoasClientConfig.class)
public interface MyFeignHateoasClient {
#RequestMapping(method = RequestMethod.GET, path = "/bookings/search/findByBookingUuid?bookingUuid={uuid}")
Resource<Booking> getBookingByUuid(#PathVariable("uuid") String uuid);
}
The client-config:
#Configuration
public class MyFeignHateoasClientConfig{
#Value("${service.user.name:bla}")
private String serviceUser;
#Value("${service.user.password:blub}")
private String servicePassword;
#Bean
public BasicAuthRequestInterceptor basicAuth() {
return new BasicAuthRequestInterceptor(serviceUser, servicePassword);
}
#Bean
public Decoder decoder() {
return new JacksonDecoder(getObjectMapper());
}
#Bean
public Encoder encoder() {
return new JacksonEncoder(getObjectMapper());
}
public ObjectMapper getObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
}
#Bean
public Logger logger() {
return new Slf4jLogger(MyFeignHateoasClient.class);
}
#Bean
public Logger.Level logLevel() {
return Logger.Level.FULL;
}
}
And in the application using the client via an jar-dependency:
#SpringBootApplication
#EnableAutoConfiguration
#EnableFeignClients(basePackageClasses=MyFeignHateoasClient.class)
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
#ComponentScan(excludeFilters = #Filter(type = ... ), basePackageClasses= {....class}, basePackages="...")
public class Application {
...
Now this is working:
#Autowired
private MyFeignHateoasClient serviceClient;
...
void test() {
Resource<Booking> booking = serviceClient.getBookingByUuid(id);
Link link = booking.getLink("relation-name");
}
Now my question:
How do I go on from here, i.e. navigate to the resource in the Link?
The Link is containing an URL on the resource I want to request.
Do I really have to parse the ID out of the URL and add a method to the FeignClient like getRelationById(id)
Is there at least a way to pass the complete resource-url to a method of a FeignClient?
I have found no examples which demonstrate how to proceed from here (despite the POST/modify). Any hints appreciated!
Thx
My current solution:
I added an additional request in the Feign client, taking the whole resource path:
...
public interface MyFeignHateoasClient {
...
#RequestMapping(method = RequestMethod.GET, path = "{resource}")
Resource<MyLinkedEntity> getMyEntityByResource(#PathVariable("resource") String resource);
}
Then I implemented some kind of "HAL-Tool":
...
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.springframework.hateoas.Link;
import feign.Target;
import lombok.SneakyThrows;
public class HalTool {
private Object feignClient;
public static HalTool forClient( Object feignClient ) {
return new HalTool(feignClient);
}
private HalTool( Object feignClient ) {
this.feignClient = feignClient;
}
#SneakyThrows
private String getUrl() {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(feignClient);
Field target = invocationHandler.getClass().getDeclaredField("target");
target.setAccessible(true);
Target<?> value = (Target<?>) target.get(invocationHandler);
return value.url();
}
public String toPath( Link link ) {
String href = link.getHref();
String url = getUrl();
int idx = href.indexOf(url);
if (idx >= 0 ) {
idx += url.length();
}
return href.substring(idx);
}
}
And then I could do request a linked resource like this:
Link link = booking.getLink("relation-name");
Resource<MyLinkedEntity> entity = serviceClient.getMyEntityByResource(
HalTool.forClient(serviceClient).toPath(link));

I am using TestRestTemplate to Test with #RequestParam value how to execute

//how to send #RequestParam value to url
enter code here#ApiRestController
public class CityController extends BaseController{
#GetMapping("/cities")
public ResponseEntity<CitiesResponse> getAll(
#RequestParam(value = "pageNumber", defaultValue = "1") int pageNumber,
#RequestParam(value = "pageSize", defaultValue = "100") int pageSize,
#RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
#RequestParam(value = "sortDirection", defaultValue = "asc", required = false) String sortDirection,
#RequestParam(value = "search", required = false) String search) {
return new ResponseEntity(cityService.getAll(pageNumber, pageSize, sortBy, sortDirection, search), HttpStatus.OK);
}
}
To easily manipulate URLs / path / params / etc., you can use Spring's UriComponentsBuilder class. It's cleaner that manually concatenating strings and it takes care of the URL encoding for you:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("pageNumber", 1)
.queryParam("pageSize", 10)
.queryParam("sortBy", "id")
.queryParam("sortDirection", "desc")
.queryParam("search", "hello search");
HttpEntity<?> entity = new HttpEntity<>(headers); //Update this as per your code
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.GET,
entity,
String.class);
There are different ways to test in spring boot. Check the samples below:
First option:
It's more like an integration test. In this case the port will be the default 8080
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {
private TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void contextLoads() {
String url = "http://localhost:8080";
URI uri = UriComponentsBuilder.fromHttpUrl(url).path("/books")
.queryParam("order", "asc").build().toUri();
this.restTemplate.getForEntity(uri, Void.class);
}
}
Second option:
Very similar to the first option but this time it will run in an random port which can be capture by #LocalServerPort
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {
#LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void contextLoads() {
String url = "http://localhost:" + this.port;
URI uri = UriComponentsBuilder.fromHttpUrl(url).path("/books")
.queryParam("order", "asc").build().toUri();
this.restTemplate.getForEntity(uri, Void.class);
}
}
UriComponentsBuilder has been used to build the uri in a very friendly way.
Third option:
This option doesn't involve TestRestTemplate but just involve the RestController by itself. Any dependency inside the controller should be mark with #MockBean in the test.
#RunWith(SpringRunner.class)
#WebMvcTest(BookRestController.class)
public class DemoApplicationTests {
#Autowired
private MockMvc mvc;
#Test
public void contextLoads() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get("/books")
.param("order", "asc"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}