Swagger UI Bad Request URL Generation - java

I've been using swagger for some time now and I have now ran into a problem where the generated request URL puts my optional parameters in the request. This is not happening on other projects I have that are using the same annotations, swagger version, and swagger maven version.
Here is a code snippet inside my #Controller
private static final String BY_USERNAME = "byUsername";
private static final String BY_NAME = "byName";
private static final String BY_PHONENUMBER = "byPhoneNumber";
private static final String BY_USERTYPENAME = "byUserTypeName";
private static final String BY_ACTIVE = "byActive";
private static final String BY_ACTIVE_AND_AVAILABLE = "byActiveAndAvailable";
#ApiOperation(value = "Read User(s)", notes = "notes removed so post wont be so long", consumes = "application/x-www-form-urlencoded")
#RequestMapping(value = "", method = RequestMethod.GET, consumes = "application/x-www-form-urlencoded")
#ApiImplicitParam(value = "Authorization Token", name = TokenAuthenticationService.HEADER_STRING, dataType = "String", paramType = "header")
#ApiResponses(value = { #ApiResponse(message = "successful read call", code = 200), #ApiResponse(message = "bad parameter provided", code = 400) })
public Page<User> readUser(
#ApiParam(name = "queryType", value = "type of query", allowableValues = BY_USERNAME + "," + BY_NAME + "," + BY_PHONENUMBER + ","
+ BY_USERTYPENAME + "," + BY_ACTIVE + "," + BY_ACTIVE_AND_AVAILABLE) #RequestParam(value = "queryType", required = true) String queryType,
#ApiParam(name = "username", value = "username of user") #RequestParam(value = "username", required = false) String username,
#ApiParam(name = "name", value = "name of user(s)") #RequestParam(value = "name", required = false) String name,
#ApiParam(name = "phoneNumber", value = "phone number of user(s)") #RequestParam(value = "phoneNumber", required = false) String phoneNumber,
#ApiParam(name = "userTypeName", value = "user type name of user(s)") #RequestParam(value = "userTypeName", required = false) String userTypeName,
#ApiParam(name = "active", value = "active status of user(s)") #RequestParam(value = "active", required = false) Boolean active,
#ApiParam(name = "available", value = "available status of user(s)") #RequestParam(value = "available", required = false) Boolean available,
Pageable pageable) {
This is very basic swagger annotations. The problem is the resulting URL in the swagger-ui looks like this:
When I attempt to use it, it puts these inside the REST request.
This of course causes spring to throw exceptions about invalid characters
I tried searching all over the internet, swagger pages, and here on stackoverflow and I could not find any mention of this error. The very weird thing is, if I change the RequestMethod to be POST it works as it should. Seems to be a bug in swagger-ui handling optional parameters. Anyone else ran into this? Is there a fix besides making the request method POST?
EDIT: The "consumes = "application/x-www-form-urlencoded"" was not in my original code. I've been debugging this and adding stuff to try and make it work lol.
Versions used:
springfox-swagger2 version 2.6.1
springfox-swagger-ui version 2.6.1

What version of the swagger-ui are you using?
Since you have enableUrlTemplating(true) in your docket you need to use the appropriate implementation of swagger-ui that supports it.
For Gradle:
compile group: 'io.springfox.ui', name: 'springfox-swagger-ui-rfc6570', version: '1.0.0'
For Maven:
<!-- https://mvnrepository.com/artifact/io.springfox.ui/springfox-swagger-ui-rfc6570 -->
<dependency>
<groupId>io.springfox.ui</groupId>
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
<version>1.0.0</version>
</dependency>
Docket supports url templating (see #21), however it is still in incubation and not officially supported by the swagger 2.0 specification. So the choices you have are:
make enableUrlTemplating(false). In which case everything will work with the exception that if you have multiple paths with different query parameters, only one of them would show up.
e.g. if you have the following urls
http://example.org/customers?firstName=A&lastName=B findCustomersByName
http://example.org/customers?email=a#b.com findCustomersByEmail
Only one of them will show up.
Keep enableUrlTemplating(true) but change the swagger-ui you're using to the one above.

Related

Is it possible to add a description for request params in Swagger?

I have an API endpoint meant to fetch appointment information. Among the parameters this endpoint takes are "from" and "to" dates, representing the date range for appointments that it will fetch. They currently show up in my Swagger like so:
I'm wondering if I might be able to augment the Swagger with a short description to explain that the endpoint will fetch appointments that have a start time that falls within this range and that the range is inclusive. Is this possible to do?
The current declaration for this endpoint in my controller looks like this
#Operation(summary = "Get the list of appointments")
#ApiResponses(value = {
#ApiResponse(
responseCode = "200", description = "List of appointments",
content = {#Content(mediaType = "application/json", schema = #Schema(implementation = AppointmentDTO.class))}
)
})
#GetMapping("/{businessId}/appointment")
public ResponseEntity<List<AppointmentDTO>> getAppointments(#PathVariable UUID businessId,
#RequestParam(required = false) List<UUID> providerIds,
#RequestParam(required = false) List<UUID> consumerIds,
#RequestParam(required = false) List<AppointmentStatus> status,
#RequestParam(required = false, name = "from") #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") Date fromDate,
#RequestParam(required = false, name = "to") #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") Date toDate,
Pageable pageable) {

How to get Page as result in Querydsl query with fetch or fetchResults properly?

Hi what i trying to achieve here is, i want to submit Pageable data into QueryDsl query and get the result as Page, how can i do it properly? here is what i do until now :
here is my controller :
#PostMapping("/view-latest-stock-by-product-codes")
public ResponseEntity<RequestResponseDTO<Page<StockAkhirResponseDto>>> findStockByProductCodes(
#RequestBody StockViewByProductCodesDto request) {
Page<StockAkhirResponseDto> stockAkhir = stockService.findByBulkProduct(request);
return ResponseEntity.ok(new RequestResponseDTO<>(PESAN_TAMPIL_BERHASIL, stockAkhir));
}
in my controller i submit StockViewByProductCodesDto which is looked like this :
#Data
public class StockViewByProductCodesDto implements Serializable {
private static final long serialVersionUID = -2530161364843162467L;
#Schema(description = "Kode gudang yang ingin di tampilkan", example = "GBKTJKT1", required = true)
private String warehouseCode;
#Schema(description = "id dari sebuah branch", example = "1", required = true)
private Long branchId;
#Schema(description = "Kode Branch", example = "JKT", required = true)
private String branchCode;
#Schema(description = "Kode Product yang merupakan kode yang di ambil dari master product", example = "[\"MCM-508\",\"TL-101\"]", required = true)
private List<String> productCodes;
#Schema(description = "Size of row per page", example = "15", required = true)
#NotNull
private int size;
#Schema(description = "Page number", example = "1", required = true)
#NotNull
private int page;
#Schema(description = "Sort by", example = "id", required = false)
private String sort;
}
and here is my service :
public Page<StockAkhirResponseDto> findByBulkProduct(StockViewByProductCodesDto request) {
String warehouseCode = request.getWarehouseCode();
Long branchId = request.getBranchId();
String branchCode = request.getBranchCode();
List<String> productCodes = request.getProductCodes();
Set<String> productCodesSet = new HashSet<String>(productCodes);
Pageable pageable = PageUtils.pageableUtils(request);
Page<StockAkhirResponseDto> stockAkhir = iStockQdslRepository.findBulkStockAkhirPage(warehouseCode, branchId, branchCode, productCodesSet, pageable);
return stockAkhir;
}
as you can see, i extract pageable information with PageUtils.pageableUtils(request), here is my pageableUtils function looked like :
public static Pageable pageableUtils(RequestKeyword request) {
int page = 0;
int size = 20;
if (request.getPage() > 0) {
page = request.getPage() - 1;
}
if (request.getSize() > 0) {
size = request.getSize();
}
if (!request.getSort().isEmpty()) {
return PageRequest.of(page, size, Sort.by(request.getSort()).descending());
} else {
return PageRequest.of(page, size);
}
}
after i got the Pageable data, i submit it into my repository, which is looked like this :
public Page<StockAkhirResponseDto> findBulkStockAkhirPage(String warehouseCode, Long branchId, String branchCode,
Set<String> productCodes, Pageable pageable) {
JPQLQuery<Tuple> query = new JPAQuery<>(em);
long offset = pageable.getOffset();
long limit = pageable.getPageSize();
QStock qStock = QStock.stock;
NumberExpression<Integer> totalQty = qStock.qty.sum().intValue();
query = query.select(qStock.productId, qStock.productCode, totalQty).from(qStock)
.where(qStock.warehouseCode.eq(warehouseCode), qStock.productCode.in(productCodes),
qStock.branchCode.eq(branchCode), qStock.branchId.eq(branchId))
.groupBy(qStock.productId, qStock.productCode);
query.limit(limit);
query.offset(offset);
QueryResults<Tuple> result = query.fetchResults();
long total = result.getTotal();
List<Tuple> rows = result.getResults();
List<StockAkhirResponseDto> stockAkhirDto = rows.stream()
.map(t -> new StockAkhirResponseDto(t.get(0, Long.class), t.get(1, String.class), t.get(2, Integer.class)))
.collect(Collectors.toList());
return new PageImpl<>(stockAkhirDto, pageable, total);
}
there is no error in my editor when viewing this my repository and i able to run my project, but when i execute my repository function, i got this error :
"org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]; nested exception is
java.lang.IllegalArgumentException:
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE,
found ',' near line 1, column 38 [select count(distinct
stock.productId, stock.productCode, stock.warehouseId,
stock.warehouseCode, stock.branchCode, stock.branchId)\nfrom
com.bit.microservices.b2b.warehouse.entity.Stock stock\nwhere
stock.warehouseCode = ?1 and stock.productCode in ?2 and
stock.branchCode = ?3 and stock.branchId = ?4]"
the problem is here, on this line :
QueryResults<Tuple> result = query.fetchResults();
when i execute that line, it give me that error, i try to get the fetchResult, because i want to get the .getTotal() for the total.
but if i execute the query with .fetch(), it worked fine, like this :
List<StockAkhirResponseDto> stockAkhirDto = query.fetch()
i got my sql result execute correctly, what did i missed here? how do i get Page result correctly?
Your problem could be related with an open QueryDSL issue. The documented issue has to do with the use of fetchCount but I think very likely could be also your case.
Consider the following comment in the mentioned issue:
fetchCount() uses a COUNT function, which is an aggregate function. Your query already has aggregate functions. You cant aggregate aggregate functions, unless a subquery is used (which is not available in JPA). Therefore this use case cannot be supported.
The issue also provides a temporary solution.
Basically, the idea is be able to perform the COUNT by creating a statement over the initial select. AFAIK it is not possible with QueryDsl and this is why in the indicated workarounds they access the underline mechanisms provided by Hibernate.
Perhaps, another thing that you can try to avoid the limitation is to create a database view for your query, the corresponding QueryDsl objects over it, and use these objects to perform the actual computation. I am aware that it is not an ideal solution, but it will bypass this current QueryDsl limitation.

Java REST API Complex Query

I have a table like this :
Now I want to create a a single REST API endpoint that returns filtered set of data:
It should correctly filter any combination of API parameters.
All parameters are optional
Look at this example : GET /api?type=s&max_price=1000&min_price=200&address=Berlin
I want to be able to filter based each parameter or combination of 2 or parameters.
How should I write my #RequestParam? This is a complex query. what is the strategy for this?
Try simple GET request like:
#GetMapping(value = "/api")
public ReturnDto test(
#RequestParam(required = false, value = "type", defaultValue = "0") String type,
#RequestParam(required = false, value = "max_price", defaultValue = "10000") int maxPrice,
#RequestParam(required = false, value = "min_price", defaultValue = "0 ") int minPrice,
#RequestParam(required = false, value = "address", defaultValue = "") int address
) {
}
If you don't need the default value, you can remove the defaultValue keyword, but then you need to change int to Integer, to allow null values.

How to add example value for body in swagger 2.0 / OPENAPI 3?

I am following the Swagger 2.0 Annotation docs , but I am unable to add example values for body.
public Response updateUserInfo(#RequestBody(description = "", required = true, content = #Content(mediaType = "application/json")}) final String userInfo)
Is there any equivalent of #ApiImplicitParams or #ExampleProperty for body.
Though the above doc states an example for #ApiResponse :
examples = { #ExampleObject(name = "boo", value = "example",summary = "example of boo", externalValue = "example of external value") }
but this is not working for body.
I have also tried this but the example does not show up:
public Response updateUserInfo(##Parameter(examples = {#ExampleObject(name = "userInfo", value = "some example")})) final String userInfo)

How to make swagger using two array elements in an example?

I have an endpoint, which takes an array of gps coordinates to perform a routing. The first point is the start and the last one the finish. How can I provide an example with two entries? Neither #ApiParam nor #ApiImplicitParam worked so far.
// I tried this as parameter annotation
#ApiParam(example = "{\"points\":[{\"lat\":52.525252, \"lon\":13.131313}, {\"lat\":25.252525, \"lon\":31.313131}]}")
// and this as method annotation
#ApiImplicitParam(example = "{\"points\":[{\"lat\":52.525252, \"lon\":13.131313}, {\"lat\":25.252525, \"lon\":31.313131}]}")
It still displays:
[
{
"lat": 50.000000,
"lon": 13.000000
}
]
Those I added as example at the class
#ApiModelProperty(required = true, notes = "latitude [-90..90]", example = "50.000000")
private double lat;
#ApiModelProperty(required = true, notes = "longitude [-180..180]", example = "13.000000")
private double lon;
Edit
The full method signature looks like this:
#PostMapping(produces = "application/json")
// Swagger annotations
#ApiOperation(value = "Basic routing without sequence optimization")
#ApiResponses(value = {
#ApiResponse(
code = 200,
message = "Sorted array of waypoints in decimal notation."
),
#ApiResponse(
code = 500,
message = "a) Coordinates are not eligible for routing, because one or more are not on the streets.\n\n" +
"b) Less than two distinct coordinates were provided.",
response = String.class
)
})
public GeoPosition[] basicRouting(#RequestBody SearchDefinition def) throws RemoteException {
...
}

Categories