I'm trying to implement paging in my REST service.
TAKE 1:
public Page<Item> getPagedItems(Pageable pageable)
This is a known bug in SpringFox / Swagger where the Swagger page shows the wrong parameter names. Plus, I just want page & size options.
TAKE 2:
public Page<Item> getPagedItems(#RequestParam(name="page", required=true) int page, #RequestParam(name="page", required=true) int size)
This gives me the correct params, but it doesn't let me set the param descriptions or example values.
TAKE 3:
#ApiImplicitParams({
#ApiImplicitParam(name="page", value="page description", required=true, example="0", dataType="int"),
#ApiImplicitParam(name="size", value="size description", required=true, example="10", dataType="int")
})
public Page<Item> getPagedItems(int page, int size)
This has yet ANOTHER bug where example="0" doesn't work, but every other value except that one works lol. If I change the datatype to String then it'll display the 0 example value, but then it'll let you put in anything in the text box. Also tried "0 ", "0.0", "0\0", etc. Stuff like that.
If I don't set examples, then Springfox throws an exception complaining about no examples lol. The closest thing I can do is put 0 for both with both blank. Ugh. Having 0 / 10 isn't a good option since the 0 doesn't work, but the 10 does.
Any idea how to get an example of 0 to work? Spring Fox doesn't seem to be too well supported, even for open source, so no response on GitHub.
Or some other way of having an example AND description?
We created a inherited annotation and used that :
#Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#ApiImplicitParams({
#ApiImplicitParam(name = "page", dataTypeClass = int.class,defaultValue = "0", example = "0", paramType = "query", value = "Results page you want to retrieve (0..N)"),
#ApiImplicitParam(name = "size", dataTypeClass = int.class ,example = "10", paramType = "query", value = "Number of records per page."),
#ApiImplicitParam(name = "sort", allowMultiple = true, dataTypeClass=String.class, paramType = "query", value = "Sorting criteria in the format: property(,asc|desc). "
+ "Default sort order is ascending. " + "Multiple sort criteria are supported.")
})
public #interface ApiPagingParams {
}
usage
#ApiPagingParams
#ResponseStatus(HttpStatus.OK)
#GetMapping("/entity")
ResponseEntity<Page<...>> getSurveyEntityList(#ApiIgnore("see #ApiPagingParams") #PageableDefault(page = 0, size = 10) Pageable pageable,...)
Related
I am using swagger with the following Maven dependency:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.9</version>
</dependency>
I wrote an API call as follows:
#Operation(
method = "GET",
summary = "Get alerts by Date Range",
extensions = #Extension(name = "x-rest-api", properties = #ExtensionProperty(name = "public", value = "true")),
parameters = {
#Parameter(name = "startDate",
in = ParameterIn.QUERY,
description = "Get alerts from this date. `startDate` should be in GMT and 24 hour Clock",
example = "2020-07-15T15:57:00Z",
schema = #Schema(implementation = ZonedDateTime.class),
required = true),
#Parameter(name = "endDate",
in = ParameterIn.QUERY,
description = "Get alerts to this date. `endDate` should be in GMT and 24 hour Clock",
example = "2020-07-20T15:57:00Z",
required = true)
},
responses = {
#ApiResponse(
responseCode = "200",
description = "A list of alerts",
content = #Content(schema = #Schema(implementation = AlertObject .class))),
#ApiResponse(
responseCode = "401",
description = "Invalid Bearer Token",
content = #Content(schema = #Schema(implementation = ApiException.class))
)
}
)
#GET
#Path("/old")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public AlertObject alertsByDateRange(#NotNull #Valid #QueryParam("startDate") ZonedDateTime startDate,
#NotNull #Valid #QueryParam("endDate") ZonedDateTime endDate) { ... }
The above 2 parameters are both supposed to be required parameters. So I set the required = true. However, once I set them to required, I was no longer able to execute this API call through swagger. When I used Postman to call this function it worked perfectly. However, I can no longer use the swagger UI to test. I don't know why that is? I even tried setting the schema field for one of them (I thought that perhaps swagger needed to know how to validate) but that didn't help. So now, when I fill out those fields, swagger highlights them in red and refuses to execute this API call. When I hover my mouse over the red box, it says "Required field is not provided".
I looked online but I cannot find a good set of examples of how to properly set required parameters in swagger for java, nor could I find an API that describes the nuances of the java version.
So my question is - how do I properly setup required parameters such that they are still executable through the swagger UI?
I figured out the issue.
If you notice in the code above, I declare the same parameter in swagger twice. The first time is:
#Parameter(name = "startDate",
in = ParameterIn.QUERY,
description = "Get alerts from this date. `startDate` should be in GMT and 24 hour Clock",
example = "2020-07-15T15:57:00Z",
schema = #Schema(implementation = ZonedDateTime.class),
required = true),
And the second time is:
#NotNull #Valid #QueryParam("startDate") ZonedDateTime startDate,
When I looked at the yaml, I saw this:
parameters:
- name: startDate
in: query
description: Get historical alerts from this date. `startDate` should be in
GMT and 24 hour Clock
required: true
schema:
type: string
format: date-time
example: 2020-07-15T15:57:00Z
...
- name: startDate
in: query
required: true
schema:
type: string
format: date-time
The parameter appears twice as a result. And you can tell which is which, because the 1st parameter has a description and the second one does not.
(I think the underlying problem is that both are considered required but we are only able to fill out 1 parameter.)
Once I remove the second declaration I was then able to use swagger to test my API calls.
I am trying to make the below elasticsearch query to work with spring data. The intent is to return unique results for the field "serviceName". Just like a SELECT DISTINCT serviceName FROM table would do comparing to a SQL database.
{
"aggregations": {
"serviceNames": {
"terms": {
"field": "serviceName"
}
}
},
"size":0
}
I configured the field as a keyword and it made the query work perfectly in the index_name/_search api as per the response snippet below:
"aggregations": {
"serviceNames": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "service1",
"doc_count": 20
},
{
"key": "service2",
"doc_count": 8
},
{
"key": "service3",
"doc_count": 8
}
]
}
}
My problem is the same query doesn't work in Spring data when I try to run with a StringQuery I get the error below. I am guessing it uses a different api to run queries.
Cannot execute jest action , response code : 400 , error : {"root_cause":[{"type":"parsing_exception","reason":"no [query] registered for [aggregations]","line":2,"col":19}],"type":"parsing_exception","reason":"no [query] registered for [aggregations]","line":2,"col":19} , message : null
I have tried using the SearchQuery type to achieve the same results, no duplicates and no object loading, but I had no luck. The below sinnipet shows how I tried doing it.
final TermsAggregationBuilder aggregation = AggregationBuilders
.terms("serviceName")
.field("serviceName")
.size(1);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("index_name")
.withQuery(matchAllQuery())
.addAggregation(aggregation)
.withSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.withSourceFilter(new FetchSourceFilter(new String[] {"serviceName"}, new String[] {""}))
.withPageable(PageRequest.of(0, 10000))
.build();
Would someone know how to achieve no object loading and object property distinct aggregation on spring data?
I tried many things without success to print queries on spring data, but I could not, maybe because I am using the com.github.vanroy.springdata.jest.JestElasticsearchTemplate implementation.
I got the query parts with the below:
logger.info("query:" + searchQuery.getQuery());
logger.info("agregations:" + searchQuery.getAggregations());
logger.info("filter:" + searchQuery.getFilter());
logger.info("search type:" + searchQuery.getSearchType());
It prints:
query:{"match_all":{"boost":1.0}}
agregations:[{"serviceName":{"terms":{"field":"serviceName","size":1,"min_doc_count":1,"shard_min_doc_count":0,"show_term_doc_count_error":false,"order":[{"_count":"desc"},{"_key":"asc"}]}}}]
filter:null
search type:DFS_QUERY_THEN_FETCH
I figured out, maybe can help someone. The aggregation don't come with the query results, but in a result for it self and is not mapped to any object. The Objects results that comes apparently are samples of the query elasticsearch did to run your aggregation (not sure, maybe).
I ended up by creating a method which can do a simulation of what would be on the SQL SELECT DISTINCT your_column FROM your_table, but I think this will work only on keyword fields, they have a limitation of 256 characters if I am not wrong. I explained some lines in comments.
Thanks #Val since I was only able to figure it out when debugged into Jest code and check the generated request and raw response.
public List<String> getDistinctField(String fieldName) {
List<String> result = new ArrayList<>();
try {
final String distinctAggregationName = "distinct_field"; //name the aggregation
final TermsAggregationBuilder aggregation = AggregationBuilders
.terms(distinctAggregationName)
.field(fieldName)
.size(10000);//limits the number of aggregation list, mine can be huge, adjust yours
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("your_index")//maybe can be omitted
.addAggregation(aggregation)
.withSourceFilter(new FetchSourceFilter(new String[] { fieldName }, new String[] { "" }))//filter it to retrieve only the field we ar interested, probably we can take this out.
.withPageable(PageRequest.of(0, 1))//can't be zero, and I don't want to load 10 results every time it runs, will always return one object since I found no "size":0 in query builder
.build();
//had to use the JestResultsExtractor because com.github.vanroy.springdata.jest.JestElasticsearchTemplate don't have an implementation for ResultsExtractor, if you use Spring defaults, you can probably use it.
final JestResultsExtractor<SearchResult> extractor = new JestResultsExtractor<SearchResult>() {
#Override
public SearchResult extract(SearchResult searchResult) {
return searchResult;
}
};
final SearchResult searchResult = ((JestElasticsearchTemplate) elasticsearchOperations).query(searchQuery,
extractor);
final MetricAggregation aggregations = searchResult.getAggregations();
final TermsAggregation termsAggregation = aggregations.getTermsAggregation(distinctAggregationName);//this is where your aggregation results are, in "buckets".
result = termsAggregation.getBuckets().parallelStream().map(TermsAggregation.Entry::getKey)
.collect(Collectors.toList());
} catch (Exception e) {
// threat your error here.
e.printStackTrace();
}
return result;
}
I am creating a REST Api using Spring boot, and auto generating the swagger documentation in controllers using swagger codegen. However, I am not able to set a description and example for a parameter of type String in a POST request. Here is mi code:
import io.swagger.annotations.*;
#Api(value = "transaction", tags = {"transaction"})
#FunctionalInterface
public interface ITransactionsApi {
#ApiOperation(value = "Places a new transaction on the system.", notes = "Creates a new transaction in the system. See the schema of the Transaction parameter for more information ", tags={ "transaction", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Another transaction with the same messageId already exists in the system. No transaction was created."),
#ApiResponse(code = 201, message = "The transaction has been correctly created in the system"),
#ApiResponse(code = 400, message = "The transaction schema is invalid and therefore the transaction has not been created.", response = String.class),
#ApiResponse(code = 415, message = "The content type is unsupported"),
#ApiResponse(code = 500, message = "An unexpected error has occurred. The error has been logged and is being investigated.") })
#RequestMapping(value = "/transaction",
produces = { "text/plain" },
consumes = { "application/json" },
method = RequestMethod.POST)
ResponseEntity<Void> createTransaction(
#ApiParam(
value = "A JSON value representing a transaction. An example of the expected schema can be found down here. The fields marked with an * means that they are required." ,
example = "{foo: whatever, bar: whatever2}")
#Valid #RequestBody String kambiTransaction) throws InvalidTransactionException;
}
The example property of the #ApiParam has been manually inserted by me, because the codegen was ignoring that part of the yaml (That is another question: why is the editor ignoring the example part?). Here is part of the yaml:
paths:
/transaction:
post:
tags:
- transaction
summary: Place a new transaction on the system.
description: >
Creates a new transaction in the system. See the schema of the Transaction parameter
for more information
operationId: createTransaction
parameters:
- $ref: '#/parameters/transaction'
consumes:
- application/json
produces:
- text/plain
responses:
'200':
description: Another transaction with the same messageId already exists in the system. No transaction was created.
'201':
description: The transaction has been correctly created in the system
'400':
description: The transaction schema is invalid and therefore the transaction has not been created.
schema:
type: string
description: error message explaining why the request is a bad request.
'415':
description: The content type is unsupported
'500':
$ref: '#/responses/Standard500ErrorResponse'
parameters:
transaction:
name: kambiTransaction
in: body
required: true
description: A JSON value representing a kambi transaction. An example of the expected schema can be found down here. The fields marked with an * means that they are required.
schema:
type: string
example:
{
foo*: whatever,
bar: whatever2
}
And finally, this is what swagger is showing:
Finally, the dependencies used in build.gradle are the following ones:
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.7.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.7.0'
So, Question is:
Does anybody know how I can set the description and an example of a body parameter using swagger annotations?
EDIT
I've achieved to change the description using #ApiImplicitParam instead of #ApiParam, but example is still missing:
#ApiImplicitParams({
#ApiImplicitParam(
name = "kambiTransaction",
value = "A JSON value representing a transaction. An example of the expected schema can be found down here. The fields marked with * means that they are required. See the schema of KambiTransaction for more information.",
required = true,
dataType = "String",
paramType = "body",
examples = #Example(value = {#ExampleProperty(mediaType = "application/json", value = "{foo: whatever, bar: whatever2}")}))})
I have similar issue with generating examples for body objects - annotation #Example and #ExampleProperty simply doesn't work for no reason in swagger 1.5.x. (I use 1.5.16)
My current solution is:
use #ApiParam(example="...") for non-body objects, e.g.:
public void post(#PathParam("userId") #ApiParam(value = "userId", example = "4100003") Integer userId) {}
for body objects create new class and annotate fields with #ApiModelProperty(value = " ", example = " "), e.g.:
#ApiModel(subTypes = {BalanceUpdate.class, UserMessage.class})
class PushRequest {
#ApiModelProperty(value = "status", example = "push")
private final String status;;
}
Actually the java doc for the example property of the #ApiParam annotation states that this is exclusively to be used for non-body parameters. Where the examples property may be used for body parameters.
I tested this annotation
#ApiParam(
value = "A JSON value representing a transaction. An example of the expected schema can be found down here. The fields marked with an * means that they are required.",
examples = #Example(value =
#ExampleProperty(
mediaType = MediaType.APPLICATION_JSON,
value = "{foo: whatever, bar: whatever2}"
)
)
)
which resulted in the following swagger to be generated for the corresponding method:
/transaction:
post:
...
parameters:
...
- in: "body"
name: "body"
description: "A JSON value representing a transaction. An example of the expected\
\ schema can be found down here. The fields marked with an * means that\
\ they are required."
required: false
schema:
type: "string"
x-examples:
application/json: "{foo: whatever, bar: whatever2}"
However this value doesn't seem to be picked up by swagger-ui. I tried version 2.2.10 and the latest 3.17.4, but neither version used the x-examples property of the swagger.
There are some references for x-example (the one used for non-body parameters) in the code of swagger-ui but no match for x-examples. That is this doesn't seem to be supported by swagger-ui at the moment.
If you really need this example values to be present, your best option currently seems to be to change your method's signature and use a dedicated domain type for the body parameter. As stated in the comments already swagger will automatically pick up the structure of the domain type and print some nice information in swagger-ui:
Have you tried the following ?
#ApiModelProperty(
value = "A JSON value representing a transaction. An example of the expected schema can be found down here. The fields marked with an * means that they are required.",
example = "{foo: whatever, bar: whatever2}")
Have a nice day
Swagger.v3 Kotlin/Micronaut example:
#Post("/get-list")
fun getList(
#RequestBody(description = "Get list of ...",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = RequestDTO::class),
examples = [ExampleObject(value = """
{
"pagination": {
"page": 0,
"perPage": 10
},
"filter": {
"property_1": "string",
"property_2": "string"
},
"sort": {
"field": "property_1",
"order": "DESC"
}
}
""")]
)]) #Body request: RequestDTO): Response<SomeDTO> { ... }
Using swagger 3.0.0 try this over the rest method
#Operation(
summary = "Finds a person",
description = "Finds a person by their Id.",
tags = { "People" },
responses = {
#ApiResponse(
description = "Success",
responseCode = "200",
content = #Content(mediaType = "application/json", schema = #Schema(implementation = Person.class))
),
#ApiResponse(description = "Not found", responseCode = "404", content = #Content),
#ApiResponse(description = "Internal error", responseCode = "500", content = #Content)
}
)
If you are using swagger 2.9.2 then Examples are not working there. These annotations are ignored
protected Map<String, Response> mapResponseMessages(Set<ResponseMessage> from) {
Map<String, Response> responses = newTreeMap();
for (ResponseMessage responseMessage : from) {
Property responseProperty;
ModelReference modelRef = responseMessage.getResponseModel();
responseProperty = modelRefToProperty(modelRef);
Response response = new Response()
.description(responseMessage.getMessage())
.schema(responseProperty);
**response.setExamples(Maps.<String, Object>newHashMap());**
response.setHeaders(transformEntries(responseMessage.getHeaders(), toPropertyEntry()));
Map<String, Object> extensions = new VendorExtensionsMapper()
.mapExtensions(responseMessage.getVendorExtensions());
response.getVendorExtensions().putAll(extensions);
responses.put(String.valueOf(responseMessage.getCode()), response);
}
return responses;
}
Try using swagger 3.0.0-Snapshot.
You need to change maven dependencies like this:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-spring-webmvc</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
and change annotation on your Swagger config file to this: #EnableSwagger2WebMvc
I have the following API endpoint:
#ApiResponses(
value = {
#ApiResponse(code = 200, message = "OK",
responseHeaders = {
#ResponseHeader(name = "X-RateLimit-Limit", description = "The defined maximum number of requests available to the consumer for this API.", response = Integer.class),
#ResponseHeader(name = "X-RateLimit-Remaining", description = "The number of calls remaining before the limit is enforced and requests are bounced.", response = Integer.class),
#ResponseHeader(name = "X-RateLimit-Reset", description = "The time, in seconds, until the limit expires and another request will be allowed in. This header will only be present if the limit is being enforced.", response = Integer.class)
}
)
}
)
#ApiOperation(httpMethod = "GET", hidden = false, nickname = "Get Network Availability in JSON", value = "Get network availability for a product", response = AvailableToPromise.class, position = 1)
#RequestMapping(value = "/{product_id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> networkAvailabilityJsonResponse(
#RequestHeader HttpHeaders headers,
#PathVariable("product_id") String productId,
#Valid NetworkAvailabilityCmd cmd, //query params
BindingResult result)
throws Exception {}
}
Certain parameters, such as key are taken from the query and mapped into this object through Spring MVC.
However, in the parameters section of my endpoint in the swagger-ui, it's showing me a few odd things:
None of the variables that are in NetworkAvailabilityCmd show in this parameters list, and cmd itself shows as being located in the request body (it's actually located in the query). Is there a way to hide cmd and extract the params inside this object to show on the params list? I'd like the params list to look like this (with more params):
I'm able to do this if I use #ApiImplicitParams on the method endpoint, and write out each of the params. However, this NetworkAvailabilityCmd is used for many endpoints, and having the list of params on each endpoint is very messy. Being able to extract the variables from in the object would be far cleaner, and would prevent people from forgetting to add the entire list to new endpoints.
I imagine that it requires an annotation on NetworkAvailabilityCmd cmd, and potentially something on the variables in that class, but I can't seem to find what I'm looking for in the docs.
Thanks!
I found out that adding #ModelAttribute worked magically. This annotation is from Spring.
this is my code
var myObj =
{
"id": 0,
"createdDate": "12-12-2014 12:00:00",
"fromEmail": "abc#gmail.com",
"sampleBooleanValue": false,
"extraDescrition":"ssfsvgsf",
"sampleArraay":[{"arrayElem1"}, {"arrayElem2"}]
};
console.log(downtime1);
$rootScope.httpPost('createMyObj/', myObj).success(function (successdata) {
console.log(successdata);
}).error(function (errordata) {
console.log(errordata);
});
I have my REST endpoint created with URI createMyObj but as soon As I hit submit I get 400-bead request - the request submitted is syntactically incorrect error.
Is my JSON in correct format?
EDIT:
Here is my corrosponding Java bean
public class MyObj {
#Id
private int id;
private String fonEmail;
#ElementCollection
private List<String> sampleArraay;
private ZonedDateTime createdDate;
private Boolean sampleBooleanValue;
private String extraDescription;
Your array from the sampleArraay field is invalid. Try:
var myObj = {
"id": 0,
"createdDate": "12-12-2014 12:00:00",
"fromEmail": "abc#gmail.com",
"sampleBooleanValue": false,
"extraDescrition":"ssfsvgsf",
"sampleArraay":["arrayElem1", "arrayElem2"]
};
console.log(downtime1);
$rootScope.httpPost('createMyObj/', myObj).success(function (successdata) {
console.log(successdata);
})
.error(function (errordata) {
console.log(errordata);
});
"sampleArraay":[{"arrayElem1"}, {"arrayElem2"}]
Looks to be wrong. Were you planning for the elements of sampleArraay to be nested objects?
Also at the risk of being flippant, the spelling in your example, words like "Array" and "Description" are wrong. Could it be a case of being spelled wrong in one place and not in the other?
One thing I like to do when I get 400 errors like this is progressively simplify the object I am trying to send by commenting out elements until I get to the culprit.
I did this on JSFiddle.com (a great resource) with your code and a simple alert statement to confirm the array problem.