I have a Restlet API application using the Restlet Swagger extenstion to generate the Swagger json via Swagger2SpecificationRestlet.
// route to generated swagger json
swagger2SpecificationRestlet.attach(router, "/docs");
My routes are defined like this:
router.attach("/path/{pathParam}", MyResource.class);
I have implemented swagger-ui locally and set the initializer url to read the swagger json from /docs. The UI works as expected for all of the routes including the required path parameter inputs however the annotations for the query params are rendering as post body(?) without any of the other defined fields. param inputs:
"parameters": [
{
"name": "pathParam",
"in": "path",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "body",
"required": false,
"schema": {
"type": "string"
}
],
My resource method with annotations:
#ApiOperation(
value="test desc",
httpMethod = "GET",
produces = "application/json",
notes="testing notes"
)
#Get("txt")
public String represent(
#ApiParam(name="queryParam", value = "testing desc")
#QueryParam("queryParam") String queryParam
) throws SQLException { ... }
How can I annotate query params so that swagger generates the correct json configuration?
After looking further into the documentation I came across this:
https://docs.swagger.io/swagger-core/apidocs/com/wordnik/swagger/annotations/ApiImplicitParam.html#paramType()
which states:
public #interface ApiImplicitParam Represents a single parameter in an
API Operation. While ApiParam is bound to a JAX-RS parameter, method
or field, this allows you to manually define a parameter in a
fine-tuned manner. This is the only way to define parameters when
using Servlets or other non-JAX-RS environments.
I replaced ApiParam with ApiImplicitParam which has a field to declare param type and moved the annotation above the method:
#ApiOperation(
value="get stuff",
httpMethod = "GET",
produces = "application/json",
notes="test notes"
)
#Get("txt")
#ApiImplicitParam(
name="queryParam",
dataType = "String",
paramType = "query",
value = "testing query param desc",
defaultValue = "default val")
public String represent() throws SQLException {
return getMethod();
}
Which results in the correctly generated json:
"parameters": [
{
"name": "pathParam",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "queryParam",
"in": "query",
"description": "testing query param desc",
"required": false,
"type": "string",
"default": "default val"
}
]
Related
I'm writing a Spring Boot application in Kotlin, and I'm currently struggling to generate a specification for a DTO class that has a backing field of the type String, which I want to then later parse into one of two enum classes in the adapter layer.
I've tried the following approach using the oneOf Annotation value, which seemed like it does what I want:
data class MyDto(
#Schema(
type = "string",
oneOf = [MyFirstEnum::class, MySecondEnum::class]
)
val identifier: String,
val someOtherField: String
) {
fun transform() { ... } // this will use the string identifier to pick the correct enum type later
}
Which results in the following OpenApi Spec:
"MyDto": {
"required": [
"someOtherField",
"identifier"
],
"type": "object",
"properties": {
"identifier": {
"type": "object", // <--- this should be string
"oneOf": [{
"type": "string",
"enum": [
"FirstEnumValue1",
"FirstEnumValue2",
"FirstEnumValue3"
]
}, {
"type": "string",
"enum": [
"SecondEnumValue1",
"SecondEnumValue2",
"SecondEnumValue3"
]
}
]
},
"someOtherField": {
"type": "string"
}
}
}
As you can see, the enum constants are (I think) correctly inlined into the specification, but the type annotation on the field, which I set to string is bypassed, resulting in an object type, which I suppose is incorrect in this case.
My questions are:
Is my current code and the resulting spec valid with the object declaration instead of string?
Is there a better way to embed the enum values into the spec?
Edited to add: I'm using Spring Boot v2.7.8 in combination with springdoc-openapi v1.6.13 to automatically generate the OpenApi Spec.
The annotation based approach that I showed in my question does not seem to generate a valid OpenApi spec with springdoc-openapi:1.6.13. The type of the field identifier needs to be String, as Helen mentioned in the comments.
I was able to solve the issue by creating the Schema for this particular class manually, using a GlobalOpenApiCustomizer Bean:
#Bean
fun myDtoCustomizer(): GlobalOpenApiCustomizer {
val firstEnum = StringSchema()
firstEnum.description = "First Enum"
MyFirstEnum.values().forEach { firstEnum.addEnumItem(it.name) }
val secondEnum = StringSchema()
secondEnum.description = "Second Enum"
MySecondEnum.values().forEach { secondEnum.addEnumItem(it.name) }
return GlobalOpenApiCustomizer {
it.components.schemas[MyDto::class.simpleName] = ObjectSchema()
.addProperty(
MyDto::identifier.name,
StringSchema().oneOf(
listOf(
firstEnum,
secondEnum
)
)
)
.addProperty(MyDto::someOtherField.name, StringSchema())
}
}
Which in turn produces the following Spec:
"MyDto": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"oneOf": [{
"type": "string",
"description": "First Enum",
"enum": [
"FirstEnumValue1",
"FirstEnumValue2",
"FirstEnumValue3"
]
}, {
"type": "string",
"description": "Second Enum",
"enum": [
"SecondEnumValue1",
"SecondEnumValue2",
"SecondEnumValue3"
]
}
]
},
"someOtherField": {
"type": "string"
}
}
}
I have the following properties file generated via Java and spring boot data elasticsearch. The file is generated in a User.java class and the property "friends" is a List where Friends is a Fiends.java file, both class file act as the model. Essentially I want to produce a select statement but in Query DSL Language using Spring Boot Data. The index is called user.
So I am trying to achieve the following SELECT * FROM User where (userName ="Tom" OR nickname="Tom" OR friendsNickname="Tom") AND userID="3793"
or (verbose-dsl)
match where (userName="Tom" OR nickname="Tom" OR friendsNickname="Tom") AND userID="3793"
"mappings": {
"properties": {
"_class": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"userName": {
"type": "text"
},
"userId": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"friends": {
"type": "nested",
"properties": {
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
},
"age": {
"type": "text"
},
"friendsNickname": {
"type": "text"
}
}
},
"nickname": {
"type": "text"
}
}
}
I have tried the following code but return 0 hits back from a elastic search but no dice returns no hits
BoolQueryBuilder query =
QueryBuilders.boolQuery()
.must(
QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("userName", "Tom"))
.should(QueryBuilders.matchQuery("nickname", "Tom"))
.should(
QueryBuilders.nestedQuery(
"friends",
QueryBuilders.matchQuery("friendsNickname", "Tom"),
ScoreMode.None)))
.must(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("userID", "3793")));
Apologies if this seems like a simple question, My knowledge on ES is quite thin, sorry if this may seem like an obvious answer.
Great start!!
You just have a tiny mistake on the following line where you need to prefix the field name by the nested field name, i.e. friends.friendsNickname
...
QueryBuilders.matchQuery("friends.friendsNickname", "Tom"),
... ^
|
prefix
Also you have another typo where the userID should read userId according to your mapping.
Use friends.friendsNickname and also user termsQuery on userId.keyword
`
.must(QueryBuilders.boolQuery()
.should(QueryBuilders.matchQuery("userName", "Tom"))
.should(QueryBuilders.matchQuery("nickname", "Tom"))
.should(QueryBuilders.matchQuery("friends.friendsNickname", "Tom"))
)
.must(QueryBuilders.termsQuery("userId.keyword", "3793"));
`
Although I recommend changing userName, userID to keyword.
"userId": {
"type": "keyword",
"ignore_above": 256,
"fields": {
"text": {
"type": "text"
}
}
}
Then you don't have to put keyword so you just have to put userId instead of userId.keyword. If you want to have full-text search on the field is use userId.text. The disadvantage of having a text type is that you can't use the field to sort your results that's why I encourage ID fields to be of type keyword.
I am using a restful webservice that gives response in json api format. There is a relationship attribute that has id and type params. Based on the id reference it displays values in the included attribute. The id is created after the two requests that process as a final output. Till then I save my data in database as one single object. Now when I fetch the data from database using rest webservice the output shows all the attributes except the included. Which I believe is because it isn't able to find the reference so not getting displayed. But in the database all the values are present perfectly. I am not sure whether json api supports multiple ids for relationship attribute or not.
Example:
Request Body:
{
"data": {
"type": "orders",
"attributes": {
"name": "new order",
"updateDate": "",
"register":"yes",
"items":[
{
"description": "newly added item",
"type": "new item",
"amount": [
{
"deliveryfee": "123",
"mrp": "456"
}
]
}
]
}
}
}
Expected Response Body:
{
"data": {
"type": "orders",
"id": "1",
"attributes": {
"name": "new order",
"updateDate": "",
},
"relationships": {
"items": {
"data": [
{
"type": "items",
"id": null
}
]
}
}
},
"included": [
{
"type": "items",
"id": null,
"attributes": {
"type": "new item",
"description": "newly added item",
"amount": [
{
"deliveryfee": "123",
"mrp": "456"
}
]
}
}
]
}
Actual Response Body:
{
"data": {
"type": "orders",
"id": "1",
"attributes": {
"name": "new order",
"updateDate": "",
},
"relationships": {
"items": {
"data": [
{
"type": "items",
"id": null
}
]
}
}
}
}
I'm not fully sure if I understand your question correctly. But let me try to answer.
The combination of a type and id is used in JSON API specification to identify a resource:
Within a given API, each resource object’s type and id pair MUST identify a single, unique resource.
null as used in your example is not a valid value for id:
The values of the id and type members MUST be strings.
The API may combine multiple identifiers used in its internal database to construct the value used for id in a JSON API document as long as it's unique for the given type. This would be a valid id value from JSON API specification point of view as long as it's guaranteed to be unique.
{
"type": "posts":
"id": "post_id:5,locale:en"
}
The API may de-serialize the ID to two different identifiers: A post with the id 5 and a local with the id "en". That would be an internal implementation detail of the API. Consumers should not care if a meaning is encoded within the id value.
The request and response bodies given in your question do not fit together. Both contain a field items. But in the request body the items field is an attribute, while its a relationship in the response.
It seems as if you are trying to create multiple resources at once. This is not support by JSON API specification v1. It is supported in the third release candidate for v1.1 of the specification through the official extension Atomic Operations.
I have the following Spring Method :
#RequestMapping(value = "/product/{productId}", method = RequestMethod.GET ,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getProductById(
//ProductDTO database query in order to retrieve productg by ID and population
final ObjectMapper objectMapper = new ObjectMapper();
final String json = objectMapper.writeValueAsString(productDTO);
return new ResponseEntity<>(json, HttpStatus.OK);
That returns a product representation in JSON format, the problem that I have is when I return this JSON , in the response I get it escaped like this:
"{\"uid\":\"test\",\"type\":\"Fragrance\",\"modifiedtime\":1575379505000,\"name\":\"test
name\",\"otherProperties\":{\"container\":false,\" ETC...``
I would need it unescaped in the response , how can I achieve this?
The reason I transform the DTO manually is because if I just return the DTO in the ResponseEntity the representation of the JSON handled by Spring is not the same as the one I get using objectMapper.writeValueAsString method , instead of getting this:
"name": "nombre de prueba",
"otherProperties": {
"container": false,
"onlineExclusive": false,
"sizeGuide": "size guide",etc..
I get this:
"name": "nombre de prueba",
"otherProperties": [
{
"key": "container",
"value": {
"type": "boolean",
"value": false
}
},
{
"key": "onlineExclusive",
"value": {
"type": "boolean",
"value": false
}
},
{
"key": "sizeGuide",
"value": {
"type": "string",
"value": "size guide"
}
},
```
In MVC, you would expect that controller will receive models as input and produce models as output. In Swagger, the latter is not a problem but I have troubles with former. I can't understand how to make Swagger build an input model from incoming GET parameters.
Consider:
"paths": {
"/search": {
"get": {
"consumes": [],
"produces": [
"application/json"
],
"parameters": [
// What goes here?
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/SearchResponse"
}
},
}
}
}
}
How do I make the generated controller's method to have a signature like:
public ResponseEntity<ResultModel> controllerGet(ModelFromParameters input);
where ModelFromParameters will have several fields corresponding to different GET parameters.
Examples mostly focus either on POST requests, or GET requests where every of 20-odd parameters are stuffed in the arguments list of method, which is obviously anti-MVC.
The API in question is a complex stateless querying system with a lot of parameters.
Assumption: you want to create an object as parameter to your service method but still pass each of the fields of the object as query parameter in the actual http request and be able to document individual fields in swagger.
e.g. GET http://localhost:8080/search?parameter1=value1¶meter2=value2
Typical Service Method Definition where each query param is defined as a parameter in the actual method
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(#RequestParam parameter1, #RequestParam parameter2) {
...
}
Modified service method with single parameter using Object (a.k.a Bean) annotated with #ModelAttribute. Though it is an object, as far as the REST API is concerned it is still the same as above.
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(#ModelAttribute FormParam formParam) {
...
}
FormParam class. You can document each field using #ApiParam
public class FormParam {
#ApiParam(value = "parameter1 - description here", required = true)
private String parameter1;
#ApiParam(value = "parameter2 - description here", required = true)
private String parameter2;
//define getter setters below
}
This is how it appears in swagger
This is the generated swagger.json snippet
"paths": {
"/search": {
"get": {
"tags": ["search-service"],
"summary": "Search with Object as parameter",
"description": "Search with Object as parameter",
"operationId": "searchUsingGET",
"consumes": ["application/json"],
"produces": ["*/*"],
"parameters": [{
"name": "parameter1",
"in": "query",
"description": "parameter1 - description here",
"required": true,
"type": "string"
},
{
"name": "parameter2",
"in": "query",
"description": "parameter2 - description here",
"required": true,
"type": "string"
}],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Not Found"
}
}
}
},
If you are manually creating the swagger.json the query parameters can be documented like
"parameters": [{
"name": "parameter1",
"in": "query",
"description": "parameter1 - description here",
"required": true,
"type": "string"
},
{
"name": "parameter2",
"in": "query",
"description": "parameter2 - description here",
"required": true,
"type": "string"
}],
I think you can't. I had also a similar problem, for list page + search, using GET with body. No way to make swagger represent this, also if it works in Tomcat; also Elasticsearch supports it. It looks like there is no plan to change this aspect in swagger. I resorted to split the two in swagger: list-without-search as GET and list+search as POST, just to put the page in the swagger documentation, also if the latter actually works also as GET.
I have no experience with swagger code generation but, if your configuration generates what you expect with POST but it doesn't with GET, then you are probably hitting the same limitation.
https://github.com/swagger-api/swagger-ui/issues/2867
https://github.com/swagger-api/swagger-ui/issues/2136
https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#operationRequestBody
The requestBody is only supported in HTTP methods where the HTTP 1.1 specification RFC7231 has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
In usual, http server(eg. tomcat, jetty) will not accept a body message within a get request. If client needs pass parameters to server by http get method, it should use query string . Last part of the url after '?' character. Query string details you can see query string
But with spring mvc help, your http request parameters in query string will be bound to your controller method parameter`s fields.So it seems like client passed a pojo parameter to server side.
Any way the parameter part should look like this:
"parameters" : [ {
"name" : "age",
"in" : "query",
"required" : false,
"type" : "integer"
}, {
"name" : "firstName",
"in" : "query",
"required" : false,
"type" : "string"
}]
Please note that "in" filed's value is "query". That means parameter is passed by query string.
I supose what you require is posting your arguments in request body, it goes like this:
...
{
"name": "files",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/FileRequest"
}
}
}
which translates to:
public Response storageFilePost(
#NotEmpty(message = "systemID is required!") #QueryParam("systemID") final String systemID,
#NotNull(message = "qParam is required!") #QueryParam("qParam") final Long qParam,
#NotNull(message = "files are required!") final List<FileRequest> files) {