specification-arg-resolver
specification-arg-resolver copied to clipboard
Document the specification interface using swagger
This is more of a question than an issue, i would love to have the specifications interface to showup in my swagger documentation, the interface is supposed to be resolved as a set of query parameters, i tried to add some swagger annotations on the interface but this didnt make it visible in the swagger docs, how would you go around this?
This is more of a question than an issue, i would love to have the specifications interface to showup in my swagger documentation, the interface is supposed to be resolved as a set of query parameters, i tried to add some swagger annotations on the interface but this didnt make it visible in the swagger docs, how would you go around this?
Hi vegegoku, I start using this library and face same issue with you. I found a work around solution as below:
@ApiOperation("Return list of customers")
@ApiImplicitParams({
@ApiImplicitParam(name = "teleSaleId", value = "Tele Sale Id", dataType = "long", example = "1", paramType = "query"),
@ApiImplicitParam(name = "saleId", value = "Sale Id", dataType = "long", example = "2", paramType = "query"),
@ApiImplicitParam(name = "saleAdminId", value = "Sale Admin Id", dataType = "long", example = "3", paramType = "query"),
@ApiImplicitParam(name = "saleManagerId", value = "Sale Manager Id", dataType = "long", example = "4", paramType = "query")
})
@GetMapping
public ResponseEntity<List<CustomerResponse>> getAllCustomer(CustomerSpec customerSpec) {
return ResponseEntity.ok(customerService.getAllCustomer(customerSpec));
}
@And({
@Spec(path = "teleSaleId", params = "teleSaleId", spec = Equal.class),
@Spec(path = "saleId", params = "saleId", spec = Equal.class),
@Spec(path = "saleAdminId", params = "saleAdminId", spec = Equal.class),
@Spec(path = "saleManagerId", params = "saleManagerId", spec = Equal.class),
})
public interface CustomerSpec extends Specification<CustomerEntity> {
}
@tinhpt94 this exactly like my solution, and those 2 interfaces are both auto-generated for me using an annotation processor.
Swagger has OperationBuilderPlugin
interface. By implementing this interface you can teach swagger about annotations that are unknown to it.
We created a component that implements it where we looked for all @Spec
and @And
annotations and add them to swagger definition.
That could be a good alternative to using @ApiImplicitParam
because you write it just once and it works for all endpoints in your project.
I was wondering if that's a good idea to make it part of the library to support swagger automatically. What do you think about this @tkaczmarzyk?
Would be awesome to modify (copy) that configuration to make it also work for springdoc, when necessary.
If I understood your question right you want that the parameters that you use for the Specification show up in the Swagger docs. I solved this in a completely different way, without any extra code or the need to implement something new.
BTW: this is Kotlin code but works in the same way for Java.
@ApiOperation("Returns all authorization")
@GetMapping("/authorization")
fun getAuthorizationListBy(
@RequestParam(required = false) referenceId: Long?,
@RequestParam(required = false) useCase: String?,
@RequestParam(required = false) status: String?,
@RequestParam(required = false) paymentMethodId: Long?,
@RequestParam(required = false) paymentMethodType: String?,
authorizationSpecification: AuthorizationSpecification
): List<AuthorizationTO> =
authorizationProvider.findAuthorizationBy(authorizationSpecification)
And my specification interface is
@And(
Spec(path = "status", spec = Equal::class),
Spec(path = "useCase", spec = Equal::class),
Spec(path = "referenceId", spec = Equal::class),
Spec(path = "paymentMethod", spec = Equal::class),
Spec(path = "paymentMethodId", spec = Equal::class)
)
interface AuthorizationSpecification : Specification<Authorization>
Hello @mdekhtiarenko I am interested in this interface to make swagger learn the annotations of this library. I would like to know if you have implemented this or have an example that could help.
First, big thanks to @mdekhtiarenko on providing the idea.
I am using the OperationCustomizer
from springdoc-openapi
, that find if the Specification
class exists in method parameter.
I am using only @Join
, @And
and @Spec
annotations for now, you can perform your own customization if needed.
The code is quite ugly and I am looking for help on refactor.
The code is in Kotlin by the way, which is easy to convert them back to Java.
build.gradle.kts
implementation("org.springdoc:springdoc-openapi-ui:1.6.9")
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.9")
implementation("org.springdoc:springdoc-openapi-security:1.6.9")
com.example.api.docs.SpecificationArgOperationCustomizer.kt
package com.example.api.docs
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.media.Schema
import io.swagger.v3.oas.models.parameters.Parameter
import net.kaczmarzyk.spring.data.jpa.web.annotation.And
import net.kaczmarzyk.spring.data.jpa.web.annotation.Join
import net.kaczmarzyk.spring.data.jpa.web.annotation.RepeatedJoin
import net.kaczmarzyk.spring.data.jpa.web.annotation.Spec
import org.springdoc.core.customizers.OperationCustomizer
import org.springframework.data.jpa.domain.Specification
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod
@Component
/**
* Catch the Specification Arg and add the specs into the springdoc
*/
class SpecificationArgOperationCustomizer : OperationCustomizer {
override fun customize(operation: Operation?, handlerMethod: HandlerMethod?): Operation? {
if (handlerMethod != null && operation != null) {
for (methodParameter in handlerMethod.methodParameters) {
for (cls in methodParameter.parameterType.interfaces) {
if (cls.name == Specification::class.qualifiedName) {
// Obtain the list of joins of the specification
val joinMap = HashMap<String, String>()
for (anno in methodParameter.parameterType.annotations) {
if (anno.annotationClass.qualifiedName == Join::class.qualifiedName) {
joinMap[(anno as Join).alias] = (anno as Join).path
}
if (anno.annotationClass.qualifiedName == RepeatedJoin::class.qualifiedName){
for (join in (anno as RepeatedJoin).value){
joinMap[join.alias] = join.path
}
}
}
// Create doc param from annotations
for (anno in methodParameter.parameterType.annotations) {
if (anno.annotationClass.qualifiedName == Spec::class.qualifiedName){
createParameter(anno as Spec, joinMap).map {
operation.addParametersItem(it)
}
}
if (anno.annotationClass.qualifiedName == And::class.qualifiedName) {
for (spec in (anno as And).value) {
createParameter(spec, joinMap).map {
operation.addParametersItem(it)
}
}
}
}
}
}
}
}
return operation
}
fun createParameter(spec: Spec, joinMap: Map<String, String>): MutableList<Parameter> {
val result = mutableListOf<Parameter>()
val paramSchema = Schema<String>()
paramSchema.type = "string"
paramSchema.setDefault(null)
for (paramString in spec.params) {
// Get the alias if any join exists, perform while loop for nested loops
var path = spec.path
while (path.contains(".")) {
val splitStr = path.split(".")
path = joinMap[splitStr[0]] + ":" + splitStr[1]
}
val newParam = Parameter()
newParam.name = paramString
newParam.description =
"Will search for parameter $path using matching method: ${spec.spec.simpleName}"
newParam.required = false
newParam.`in` = "query"
newParam.schema = paramSchema
result.add(newParam)
}
return result
}
}
Finally, you can hide the specification parameter by adding annotation @Parameter(hidden = true)
to it, so that the annotation will not be shown in the UI and the json files
@Parameter(hidden = true) exampleSpecification: ExampleSpecification?,
The final result should be something like this
included in https://github.com/tkaczmarzyk/specification-arg-resolver/pull/142, will be released today in v2.12.0