-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Imagine that we have searchable api which accepts "request" and "pageable" like
/api/1.0/search?sort=id,desc&size=20000&name=feign&version=10.3.0
In that case I can't use "@QueryMap", because it should present only on one parameter.
This is our REST API interface:
@Headers(value = ["Accept: application/json", "Content-Type: application/json"])
interface ControllerApi {
@RequestLine("GET /api/1.0/search?{request}&{pageable}")
fun someEndpoint(
@Param(
value = "request",
expander = GenericDataClassExpander::class
) request: SearchRequestDto,
@Param(
value = "pageable",
expander = PageableExpander::class) pageable: Pageable
)This is our custom DTO definition:
data class SearchRequestDto(
@field:ApiModelProperty("search by name contains")
@field:JsonProperty("name")
var name: String? = null,
@field:ApiModelProperty("search by version contains")
@field:JsonProperty("version")
var version: String? = null
)Pageable is Spring commonly used interface.
These are the expanders that we are using to generate the final request:
class PageableExpander : Param.Expander {
override fun expand(value: Any): String {
val pageable = value as? Pageable
?: throw IllegalStateException("Error while expanding Pageable")
val page = pageable.pageNumber
val size = pageable.pageSize
val sort = pageable.sort
var res = ""
res += "page=$page&"
res += "size=$size&"
if (sort != null) {
res += "sort=$sort"
}
return res
}
}
class GenericDataClassExpander : Param.Expander {
override fun expand(value: Any): String {
val res = value::class.declaredMemberProperties.fold("") { acc, field ->
field as KProperty1<Any, *>
val fieldValue = field.get(value)
if (fieldValue != null) {
acc + "${field.name}=$fieldValue" + "&"
} else {
acc
}
}
return res.removeSuffix("&")
}
}The problem that we faced is that while using this API, Feign is unable to construct a proper QueryTemplate, since the CollectionFormat is supposed to be something like ?[key]=[value], e.g. key1={value1}&key2={value2}. However, we would like to omit having keys and just include already expanded values like ?{expanded-value}.
Therefore, if we do not follow the standart CollectionFormat ?[key]=[value], the QueryTemplate gets constructed with empty templateChunks, so the getVariables method returns empty array.
Then, in feign.Contract we could see this:
if (!data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}In our case when getVariables is empty, it will add our query parameters to form parameters and therefore to the body of RequestTemplate, which is not what we want.
When trying to run this request via the OkHttpClient, we will receive an exception that "method GET must not have a request body."
This behavior seems strange to me, since it basically violates the standards. And this is obviously not the behavior what I want when I generate the custom request line myself with expanders.
Maybe you could consider adding a simple condition to prevent adding any form params for the GET request like this?
HttpMethod method = HttpMethod.valueOf(data.template().getMethod());
if (method != HttpMethod.GET || !data.template().hasRequestVariable(name)) {
data.formParams().add(name);
}I would love to hear any response of this, but please be aware that I cannot change the API that I am using and I have to stick with it.