Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not preserving the parameter order when creating API using Java Configuration in springfox 2.9.2 #2705

Closed
Manish794 opened this issue Sep 28, 2018 · 15 comments

Comments

@Manish794
Copy link

I was using Java Configuration to create the client and was using the generated client in other application.
When I updated the springfox version 2.9.2 then in the new client the parameter order is changes. It is in sorted order now. It is breaking my integration as in older version, the parameter was in the order as listed in API
Please let me know what is the solution to preserve the parameter order as it is ?

@Manish794
Copy link
Author

I can see there is another thread
2.9.0 change the order of parameters in operations #2418
Please provide some option to choose whether to sort the parameter alphabetically or preserve the operation parameter order.
If it uses Map then LinkedHashMap preserve the insertion order. Or choose some Map that preserve the order as it is.

@fathzer
Copy link

fathzer commented Oct 2, 2018

Here is a (ugly) workaround:
Copy the source code of class springfox.documentation.service.Operation in a class with the same name in your application. Then replace the following lines of the constructor:

this.parameters = FluentIterable.from(parameters)
.toSortedList(byParameterName());

by

this.parameters = FluentIterable.from(parameters).toList();

You may also remove the byParameterName() method, which became useless or implement your own comparator if you want to customize parameters order.

@wwerner
Copy link

wwerner commented Nov 14, 2018

Any chance to get this in before 3.0? This (and #2418) hit us pretty bad.
Please let me know if I can help in any way.

@dilipkrish
Copy link
Member

@wwerner working on it, technically the order is not guaranteed as I've mentioned many times before. It is a Map just like a Map in any language doent preserve order

@plathub
Copy link

plathub commented Dec 11, 2018

It is a Map just like a Map in any language doent preserve order

@dilipkrish I suggest a LinkedHashMap or a TreeMap. A LinkedHashMap keeps the keys in the order they were inserted, while a TreeMap is kept sorted via a Comparator or the natural Comparable ordering of the elements. https://stackoverflow.com/a/683524/256544

@aermicioi
Copy link

Is there any update for this issue?

@swachter
Copy link

swachter commented Jan 23, 2020

A workaround is to use a ParameterBuilderPlugin and prefix the parameter name by its parameter index. An implementation in Scala is:

import com.google.common.base
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import springfox.documentation.service.PathDecorator
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spi.service.ParameterBuilderPlugin
import springfox.documentation.spi.service.contexts.{DocumentationContext, ParameterContext, PathContext}
import springfox.documentation.swagger.common.SwaggerPluginSupport

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
class CustomParameterBuilderPlugin extends ParameterBuilderPlugin {
  import CustomParameterBuilderPlugin._
  override def apply(parameterContext: ParameterContext): Unit = {
    val idx = parameterContext.resolvedMethodParameter().getParameterIndex
    val oldName = parameterContext.resolvedMethodParameter().defaultName().get()
    val newName = indexedName(oldName, idx)
    parameterContext.parameterBuilder().name(newName)
  }

  override def supports(delimiter: DocumentationType): Boolean = true
}

object CustomParameterBuilderPlugin {

  def indexedName(name: String, idx: Int): String = f"p$idx%02d_$name"

}

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
class CustomPathDecorator extends PathDecorator {
  import CustomParameterBuilderPlugin._
  override def decorator(context: PathContext): base.Function[String, String] = { path =>
    val regex = """\{([^{]+)\}""".r
    val newPath = regex.findAllMatchIn(path).zipWithIndex.foldLeft(path) { case (path, (regexMatch, idx)) =>
      val newName = indexedName(regexMatch.group(1), idx)
      path.replace(regexMatch.group(0), s"{$newName}")
    }
    newPath
  }

  override def supports(delimiter: DocumentationContext): Boolean = true
}

The tricky part is that not only the parameter names are adjusted but the request mapping pattern has to be adjusted too. Fortunately this can be done by a CustomPathDecorator that adds the index prefix to parameter names that occur in curly braces inside the path.

Note: This does work for path parameters only. In case of query parameters the names must not be changed. Otherwise the server would not recognize the query paramenters.

@swachter
Copy link

swachter commented Jan 24, 2020

After some more thoughts I came to the conclustion, that keeping the order of parameters when generating the api doc would not solve the general problem. Relying on the allignment of parameters in client code and backend code would be brittle anyway.

Therefore I solved this problem by refering to parameter names in client code. The client code is generated by swagger codegen. I had to tweak the typescript-angular template somewhat to use structural argument decomposition if more than one argument must be given to a call. An example of a generated method is:

    public webContentGet(args: { page?: number, size?: number }, observe: any = 'body', reportProgress: boolean = false ): Observable<any>

Now the method can be called in the following way:

webContentGet({ page: 0, size: 10});

@aermicioi
Copy link

Thx for solution. This still doesn't solve original issue which is the fact that arguments are being reordered, when most expect for them to not to. Some apis could be relying on the order of arguments for clarity, or already have clients that were generated based on previous springfox generated open api specs, without alphabetical reorder. Also note that not all languages have structural argument decomposition.

@cesarblum
Copy link

@fathzer I came across your solution in #2705 (comment), but I don't understand it. How will Springfox run the code in the class in my application, instead of its own?

@fathzer
Copy link

fathzer commented Feb 12, 2020

@cesarblum You should keep the same class name and package (springfox.documentation.service.Operation).
Then, there is a conflict between your class and Springfox one.
If you use maven to build your project, as your class is in your project code, it will have the priority over Springfox one during the jar build. The jar will then contain only your class.
If you use the springfox jar in the classpath, you have to ensure that your class (or your jar containing your class) is mentioned before Springfox. The java classloader will choose your class when the springfox.documentation.service.Operationis required.

@vilmrcs
Copy link

vilmrcs commented Apr 17, 2020

Here is a (ugly) workaround:
Copy the source code of class springfox.documentation.service.Operation in a class with the same name in your application. Then replace the following lines of the constructor:

this.parameters = FluentIterable.from(parameters)
.toSortedList(byParameterName());

by

this.parameters = FluentIterable.from(parameters).toList();

You may also remove the byParameterName() method, which became useless or implement your own comparator if you want to customize parameters order.

Thanks man, Problem Solved.

@dilipkrish
Copy link
Member

Fixed via #2022

@schrek1
Copy link

schrek1 commented Jul 14, 2020

looks still broken

@karaelf33
Copy link

Yes , still broken

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests