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

Fix Spring encoding of + character #16732

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

blaghed
Copy link

@blaghed blaghed commented Oct 5, 2023

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package 
    ./bin/generate-samples.sh ./bin/configs/*.yaml
    ./bin/utils/export_docs_generators.sh
    
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    For Windows users, please run the script in Git BASH.
  • File the PR against the correct branch: master (upcoming 7.1.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@blaghed
Copy link
Author

blaghed commented Oct 5, 2023

Tagging according to the technical committee list.

@cachescrubber (2022/02) @welshm (2022/02) @MelleD (2022/02) @atextor (2022/02) @manedev79 (2022/02) @javisst (2022/02) @borsch (2022/02) @banlevente (2022/02) @Zomzog (2022/09) @martin-mfg (2023/08)

Copy link
Contributor

@martin-mfg martin-mfg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @blaghed, thank you for the PR!
I've added a few small comments below.

In addition, can you please show what problem your PR fixes? So someone can add a test case for that later. Or you could even add a test case yourself if you want. :)

PS: Ok, I noticed now that the problem is described in #16733. However, I can't reproduce it. Using the files from this gist and the latest commit on the master branch of OpenAPI Generator, I get output like this (note the encoded "+" sign: "url": "https://httpbin.org/anything?myParam=2023-10-07T17%3A34%3A02.6676887%2B02%3A00").

@@ -682,31 +659,19 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
* @param returnType The return type into which to deserialize the response
* @return ResponseEntity<T> The response of the chosen type
*/
public <T> ResponseEntity<T> invokeAPI(String path, HttpMethod method, Map<String, Object> pathParams, MultiValueMap<String, String> queryParams, Object body, HttpHeaders headerParams, MultiValueMap<String, String> cookieParams, MultiValueMap<String, Object> formParams, List<MediaType> accept, MediaType contentType, String[] authNames, ParameterizedTypeReference<T> returnType) throws RestClientException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method can still throw RestClientException - see the last line of this method. Although it's not necessary to declare this, because it's derived from RuntimeException, it might still be a useful info.

So I'd recommend to restore the "throws RestClientException", although it's not really important.

public String generateQueryUri(MultiValueMap<String, String> queryParams, Map<String, Object> uriParams) {
StringBuilder queryBuilder = new StringBuilder();
public MultiValueMap<String, String> processQueryParams(MultiValueMap<String, String> queryParams, Map<String, Object> uriParams) {
final MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
queryParams.forEach((name, values) -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we need to protect against the case that queryParams == null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: This also applies to the other changed file.

public String expandPath(String pathTemplate, Map<String, Object> variables) {
return restTemplate.getUriTemplateHandler().expand(pathTemplate, variables).toString();
}

/**
* Include queryParams in uriParams taking into account the paramName
*
* @param queryParams The query parameters
* @param uriParams The path parameters
* return templatized query string
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update this JavaDoc comment like you did in the other file.

@blaghed
Copy link
Author

blaghed commented Oct 12, 2023

Hi @martin-mfg ,

First off, thank you very much for having a look. I'll be sure to address your code comments shortly.
Sorry that it took me a few days to reply to you, for some reason I haven't been getting emails for your comments here.

As for this:

PS: Ok, I noticed now that the problem is described in #16733. However, I can't reproduce it. Using the files from this gist and the latest commit on the master branch of OpenAPI Generator, I get output like this (note the encoded "+" sign: "url": "https://httpbin.org/anything?myParam=2023-10-07T17%3A34%3A02.6676887%2B02%3A00").

I decided to create a totally blank project and have only exactly the code you put in gist, and it behaved exactly as you said.
Going back to my more complex project, though, I still had the behaviour as I described in the bug report (btw, sorry if I didn't link the bug report with this PR correctly).

So, manually debugging, I found this as the reason why I could see totally different results:

    public ApiClient() {
        this.restTemplate = buildRestTemplate();
        init();
    }

    @Autowired
    public ApiClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
        init();
    }

    protected RestTemplate buildRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        // This allows us to read the response more than once - Necessary for debugging.
        restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));

        // disable default URL encoding
        DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
        uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        restTemplate.setUriTemplateHandler(uriBuilderFactory);
        return restTemplate;
    }

The important line above is:

uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);

So, if this is set on the RestTemplate, then no issues!
However, on my more complex project I am passing in my own RestTemplate as it has a bunch of additional configurations on it, and that one just keeps the default encoding that a normal RestTemplate will use.

In fact, you can also easily reproduce it simply by adding in an empty RestTemplate:

    public static void main(String[] args) {
        DefaultApi apiInstance = new DefaultApi(new ApiClient(new RestTemplate()));
        String result = apiInstance.anythingGet(OffsetDateTime.now());
        System.out.println(result);
    }

It could be that perhaps the expectation from your side is that we always set the EncodingMode?
Still feels like a bug to me, but please clarify if maybe I am just using it wrong.

@blaghed
Copy link
Author

blaghed commented Oct 12, 2023

@martin-mfg ,

In addition, can you please show what problem your PR fixes? So someone can add a test case for that later. Or you could even add a test case yourself if you want. :)

I would love to add a test!
Sorry for the ignorance, but where would a test of this nature go on this project?
I don't mind creating a structure from blank, but would ofc prefer if you already had a similar suite present that I can cheat off of and just add in this particular case to it (so lazy 🥇 ).

@martin-mfg
Copy link
Contributor

It could be that perhaps the expectation from your side is that we always set the EncodingMode?
Still feels like a bug to me, but please clarify if maybe I am just using it wrong.

The question how the implementation should behave by default I'd leave to @wing328.

I would love to add a test!
Sorry for the ignorance, but where would a test of this nature go on this project?

Now that I have a better understanding of the problem that's adressed in this PR, I no longer think a test is necessary. At least it's not a common practice so far in this repo to have extensive unit tests for the generated code.

@blaghed
Copy link
Author

blaghed commented Oct 30, 2023

@martin-mfg , so... anything else I can do to assist in merging these changes in?

@martin-mfg
Copy link
Contributor

I don't have the rights to merge PRs. Please ask the project maintainer @wing328 to merge. (And to decide what the default behaviour should be.) Outside of GitHub you can also try to reach him on Slack.

@jorgerod
Copy link
Contributor

Hi @blaghed @martin-mfg @wing328

I have tested these changes and API compatibility with spring-web 6 Observation API is lost (see this PR).

Please do not merge this PR until it is fixed.

@jorgerod
Copy link
Contributor

jorgerod commented Nov 22, 2023

On the other hand, an error is occurring when the query param has a special character, for example this case: $filter

paths:
  /v1.0/users:
    description: Get users by filter
    summary: Get users by filter
    get:
      summary: Get users by filter
      description: Get users by filter
      operationId: getUsers
      parameters:
        - name: $filter #<-- see $
          in: query
          required: true
          description: The filter to search users
          schema:
            type: string
      responses:
        200:
          description: The requested users
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsersResponse'
 components:
  schemas:
    UsersResponse:
      description: Represents the users response
      properties:
        value:
          type: array
          minItems: 0
          nullable: false
          items:
            $ref: '#/components/schemas/User'
    User:
      type: object
      properties:
        id:
          description: The identifier of the user
          type: string

I think the problem is this line in the ApiClient:

final String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8);

I think thath we should delegate the encoding form to RestTemplate, it should not be done in the ApiClient. (see URI Encoding )

@blaghed
Copy link
Author

blaghed commented Nov 22, 2023

Hi @jorgerod ,

I've just tried the example above with the changes in this PR and it generated the following method for me without error:

getUsers(String $filter)

Did you mean that the error happens elsewhere?

As for the Observability topic you mentioned, I have no clue why this PR in particular would impact that at all. What this does is just change the encoding, and not how the request is actually made.
Could you kindly expand a bit on it, please?

@jorgerod
Copy link
Contributor

Hi @blaghed

As for the Observability topic you mentioned, I have no clue why this PR in particular would impact that at all. What this does is just change the encoding, and not how the request is actually made. Could you kindly expand a bit on it, please?

The issue of observability is very well explained in this issue:

#15143

I have downloaded the demo of this issue and with the modifications in the ApiClient of this PR there is a regression failure in the metrics.

# HELP http_client_requests_active_seconds  
# TYPE http_client_requests_active_seconds summary
http_client_requests_active_seconds_active_count{client_name="localhost",exception="none",method="GET",outcome="UNKNOWN",status="CLIENT_ERROR",uri="none",} 0.0
http_client_requests_active_seconds_duration_sum{client_name="localhost",exception="none",method="GET",outcome="UNKNOWN",status="CLIENT_ERROR",uri="none",} 0.0

With openapi-generator 7.1.0, uri is filled:

# HELP http_client_requests_active_seconds  
# TYPE http_client_requests_active_seconds summary
http_client_requests_active_seconds_active_count{client_name="localhost",exception="none",method="GET",outcome="UNKNOWN",status="CLIENT_ERROR",uri="/app-rest/pet2/{petId}",} 0.0
http_client_requests_active_seconds_duration_sum{client_name="localhost",exception="none",method="GET",outcome="UNKNOWN",status="CLIENT_ERROR",uri="/app-rest/pet2/{petId}",} 0.0

I've just tried the example above with the changes in this PR and it generated the following method for me without error:

getUsers(String $filter)

Did you mean that the error happens elsewhere?

Regarding this issue, there are two causalities.

  1. new ApiClient(): A RestTemplate is created in the ApiClient with encoding_mode=VALUES_ONLY.
  2. new ApiClient(myRestTemplate): An own rest template is used to do the encoding. There the default value is URI_COMPONENT. It is in this case where the failure is.
    I think we should remove the URL.encoded from ApiClient but maybe this is out of scope of the PR.

@blaghed
Copy link
Author

blaghed commented Nov 24, 2023

Hi @jorgerod ,

Thank you for explaining, now it is clear.

RequestEntity.method(HttpMethod method, URI url)
RequestEntity.method(HttpMethod method, String uriTemplate, Object... uriVariables)
RequestEntity.method(HttpMethod method, String uriTemplate, Map<String, ?> uriVariables)

It seems that these overload methods are not working equally in the Observability layer. Likewise other methods from RestTemplate seem to leave this uri label empty.

Is this an error in how I try to use it, or a bug in Spring, though?
I've tried searching for an explanation in the diverging behaviour but can't find any. Also, the "uri" information is always present, so it is not like the Observability layer explicitely needs the "uriVariables" in order to calculate it... so strange...


Edit:

Continuing on this topic, is the WebClient approach approach ok with you?

@blaghed
Copy link
Author

blaghed commented Nov 27, 2023

Hi @jorgerod ,

Decided to test WebClient and the new RestClient, and noticed that the behaviour is exactly the same among them.
It seemed weird to me that Spring would make the same bug on all of these, so kept at it until I noticed that the metric we are referencing in this conversation is http_client_requests_active_seconds, not http_client_requests_seconds.

With both http_client_requests_seconds and http_server_requests_seconds (as oposed to http_server_requests_active_seconds), I can see the information as expected, regardless of the overload function used here.
Could it be this is a simple misunderstanding on which metric to track on your test?

Many thanks for looking at this.
Cheers

@jorgerod
Copy link
Contributor

Hi @blaghed

Honestly, after checking it a lot, I think there is no bug.

For the case of an own resttemplate, what you have to do is to configure the uriTemplateHandler correctly. For example:

    @Bean
    public RestTemplate restClient(RestTemplateBuilder builder) {
        RestTemplate restTemplate= builder.build();

        // Config uriTemplateHandler
        DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
        uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        restTemplate.setUriTemplateHandler(uriBuilderFactory);
        
        return restTemplate;
    }

    @Bean
    public PetApi petApi(RestTemplate restClient, @Value("${server.port}") String port) {
        ApiClient apiClient = new ApiClient(restClient);
        apiClient.setBasePath("http://localhost:" + port + "/app-rest");
        
        PetApi petApi = new PetApi(apiClient);
        return petApi;
    }

With this configuration, the + problem is solved, correct?

@blaghed
Copy link
Author

blaghed commented Nov 28, 2023

Hi @jorgerod ,

Yes, that would solve the problem for the OpenApi Generator use cases.
However, such a replacement would also impact any other usage of RestTemplate, which I believe is also the reason this isn't just blindly done for any RestTemplate that is passed in to the ApiClient.

So, yes, this is a bug. And yes, there are ways to work around it in order to mitigate its impact, so it is not a world-breaking bug.
In my humble opinion, this is actually an issue with Spring Framework, so I opened a ticket there. Sadly, however, it gained no traction there and the alternative they suggest is that the caller of the framework can simple handle it themselves.
spring-projects/spring-framework#31363
In this particular case, the generated code is the caller of the framework, so trying to solve it here. Kicking the ball further down to change the RestTemplate entirely to continue working around this issue would be an unfortunate conclusion to this issue.

Note that the proposed code here is a solution I am already using via the template override mechanisms, and haven't encountered issues with it so far, including the ones mentioned by you here.
But, regardless of outcome, I do appreciate your thoroughness and will tackle any further concern you may have in the hopes of getting this solved.

Cheers

@jorgerod
Copy link
Contributor

jorgerod commented Nov 29, 2023

Yes, that would solve the problem for the OpenApi Generator use cases.
However, such a replacement would also impact any other usage of RestTemplate, which I believe is also the reason this isn't just blindly done for any RestTemplate that is passed in to the ApiClient.

Sure, that's why if you look at the example I put in the previous comment, I create a RestTemplate for a specific ApiClient with their specific configuration. If you have other RestTemplate in the project, they would not be affected.

In addition, in the default configuration of the ApiClient, a RestTemplate is being created with the encoding VALUES_ONLY

protected RestTemplate buildRestTemplate() {
{{#withXml}}List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
{{#openApiNullable}}
xmlMapper.registerModule(new JsonNullableModule());
{{/openApiNullable}}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper));
RestTemplate restTemplate = new RestTemplate(messageConverters);
{{/withXml}}{{^withXml}}RestTemplate restTemplate = new RestTemplate();{{/withXml}}
// This allows us to read the response more than once - Necessary for debugging.
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));
// disable default URL encoding
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}

In fact, according to spring documentation:

The WebClient and the RestTemplate expand and encode URI templates internally through the UriBuilderFactory strategy. Both can be configured with a custom strategy, as the following example shows:

String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

The DefaultUriBuilderFactory implementation uses UriComponentsBuilder internally to expand and encode URI templates. As a factory, it provides a single place to configure the approach to encoding, based on one of the below encoding modes:

TEMPLATE_AND_VALUES: Uses UriComponentsBuilder#encode(), corresponding to the first option in the earlier list, to pre-encode the URI template and strictly encode URI variables when expanded.

VALUES_ONLY: Does not encode the URI template and, instead, applies strict encoding to URI variables through UriUtils#encodeUriVariables prior to expanding them into the template.

URI_COMPONENT: Uses UriComponents#encode(), corresponding to the second option in the earlier list, to encode URI component value after URI variables are expanded.

NONE: No encoding is applied.

In my opinion, what they mean is that the responsible for doing the expanded and encoding URI templates is the RestTemplate/WebClient itself and they say how to configure it. We should not take that logic to the ApiClient layer. ApiClient should be encoding agnostic.

Anyway, as the PR is currently, we would be breaking the observability in spring and could even cause an OOM because the url's would not be templatized.

The key is here (RestTemplate.java line 871):

@Nullable
	@SuppressWarnings("try")
	protected <T> T doExecute(URI url, @Nullable String uriTemplate, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

		Assert.notNull(url, "url is required");
		Assert.notNull(method, "HttpMethod is required");
		ClientHttpRequest request;
		try {
			request = createRequest(url, method);
		}
		catch (IOException ex) {
			ResourceAccessException exception = createResourceAccessException(url, method, ex);
			throw exception;
		}
		ClientRequestObservationContext observationContext = new ClientRequestObservationContext(request);
               
                // Observation in spring needs an uriTemplate
		observationContext.setUriTemplate(uriTemplate); 
		Observation observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(this.observationConvention,
				DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).start();

//...

This is why the BodyBuilder is created with this method:

final BodyBuilder requestBuilder = RequestEntity.method(method, UriComponentsBuilder.fromHttpUrl(basePath).toUriString() + finalUri, uriParams);

In short, I still think that this PR is not correct.

How do you see it @wing328 @cachescrubber @welshm @MelleD @atextor @manedev79 @javisst @borsch
@Zomzog @martin-mfg?

@MelleD
Copy link
Contributor

MelleD commented Nov 29, 2023

I'am using the FeignClient and not the WebClient/RestTemplate. There the + is handled correctly.
But maybe this class could help you
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/UriUtils.html

@blaghed
Copy link
Author

blaghed commented Nov 30, 2023

@MelleD , thanks for the suggestion, but I am trying to fix the Client side of the call and on the query param value, not on the URI itself. The fact that this PR impacts the URI Templating was just a side-effect of trying to get it fixed.

@jorgerod , fair enough, I can understand not liking the PR proposal, as long as we agree there is a bug and try to fix it.

So, being constructive here, how would you feel about changing this instead, then?
https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/Java/libraries/resttemplate/ApiClient.mustache#L652-L656

Suggestion:

                        if (value != null) {
                            String parsedValue = value.toString();
                            if (restTemplate.getUriTemplateHandler() instanceof DefaultUriBuilderFactory templateHandler) {
                                if (templateHandler.getEncodingMode() == DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT) {
                                    parsedValue = URLEncoder.encode(parsedValue, StandardCharsets.UTF_8);
                                }
                            }
                            String templatizedKey = encodedName + valueItemCounter++;
                            uriParams.put(templatizedKey, parsedValue);
                            queryBuilder.append('=').append("{").append(templatizedKey).append("}");
                        }

Alternatively, if we avoid the RequestEntity.method approach and rely on the direct RestTemplate methods to build the request, then the observability bug is not triggered, and Spring fills in the appropriate URI vs Template fields there.

Otherwise, I've no clue where else to fix this properly.
Spring says to fix it with the code we have in this PR:
spring-projects/spring-framework#31363 (comment)

The Spring Framework code you're pointing at is indeed linked to #19394. + is indeed allowed in the query parameter.

For this case, you should know that only URI template variables are strongly encoded; the other parts of the URI template are simply checked for allowed characters. Here, "+" is allowed so it's not encoded.

I've tested this successfully:

URI uri = UriComponentsBuilder.fromHttpUrl("https://example.org/test")
				.queryParam("time", "{time}")
				.encode()
				.buildAndExpand("2023-10-04T17:30:49.4301344+02:00")
				.toUri();

assertThat(uri).asString()
		.isEqualTo("https://example.org/test?time=2023-10-04T17%3A30%3A49.4301344%2B02%3A00");

URI encoding can be tricky, and we have a dedicated section in the reference docs for that.

You say this is incorrect, so ¯\_(ツ)_/¯
Just ignore the error? Or at least add a Qualifier on the constructor that autowires the RestTemplate so we can properly customize it?

Edit:
Amended DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES to DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT, as that is actually the RestTemplate default and the one causing encoding issues -- DefaultUriBuilderFactory's default is TEMPLATE_AND_VALUES, but this gets replaced.

@MelleD
Copy link
Contributor

MelleD commented Nov 30, 2023

@blaghed yes i know, but maybe you should look into other clients how they solved it. And yes you could also encode the raw uri with an interceptor (what I know from the RestTemplate) like here
https://www.baeldung.com/spring-resttemplate-uri-variables-encode#using-interceptors-with-resttemplate

@jorgerod
Copy link
Contributor

jorgerod commented Dec 5, 2023

Sorry for the delay in answering but I'm quite busy.

Or at least add a Qualifier on the constructor that autowires the RestTemplate so we can properly customize it?

I think it is the right thing to do and it already exists (I add the example again). You have to remember that by default, ApiClient does not have the @Component annotation and, therefore, you have to define the bean.

    // The user defines a resttemplate that will be used for the ApiClient with the configuration he wants, in this case VALUES_ONLY
    @Bean
    public RestTemplate myRestTemplate(RestTemplateBuilder builder) {
        RestTemplate restTemplate= builder.build();

        // Config uriTemplateHandler
        DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
        uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
        restTemplate.setUriTemplateHandler(uriBuilderFactory);
        
        return restTemplate;
    }

    // Uses the resttemplate defined above and will correctly encode `+`.
    @Bean
    public PetApi petApi(RestTemplate myRestTemplate) {
        ApiClient apiClient = new ApiClient(restClient);
        
        PetApi petApi = new PetApi(apiClient);
        return petApi;
    }

In this way, the encoding works perfectly and does not break Spring's observability.

@blaghed
Copy link
Author

blaghed commented Dec 5, 2023

Hi @jorgerod ,

Sorry for the delay in answering but I'm quite busy.

No worries.

I think it is the right thing to do and it already exists (I add the example again).

It is using the default RestTemplate, with no qualifier.

You have to remember that by default, ApiClient does not have the @component annotation and, therefore, you have to define the bean.

It was removed at same point, then it was added again based on the "generateClientAsBean" flag, which (as far as I can tell) is enabled by default.
This is something I like, so great that it was re-added, too.

{{#generateClientAsBean}}
@Component("{{invokerPackage}}.ApiClient")
{{/generateClientAsBean}}
public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {

In this way, the encoding works perfectly and does not break Spring's observability.

Like I mentioned, that is a workaround.

@jorgerod
Copy link
Contributor

jorgerod commented Dec 5, 2023

Hi @blaghed

It is using the default RestTemplate, with no qualifier.

Maybe you could add a property to configure the encoding of the resttemplate that ApiClient uses.

protected RestTemplate buildRestTemplate() {
{{#withXml}}List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
{{#openApiNullable}}
xmlMapper.registerModule(new JsonNullableModule());
{{/openApiNullable}}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper));
RestTemplate restTemplate = new RestTemplate(messageConverters);
{{/withXml}}{{^withXml}}RestTemplate restTemplate = new RestTemplate();{{/withXml}}
// This allows us to read the response more than once - Necessary for debugging.
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory()));
// disable default URL encoding
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory();
uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
restTemplate.setUriTemplateHandler(uriBuilderFactory);
return restTemplate;
}

It was removed at same point, then it was added again based on the "generateClientAsBean" flag, which (as far as I can tell) is enabled by default.
This is something I like, so great that it was re-added, too.

By default, ApiClient does not have @Component. I know this because I made this contribution... (docs)

image

I have already stated my point of view several times. RestTemplate provides the mechanisms to do the encoding correctly. There are millions of projects using RestTemplate (without ApiClient) and they work transparently, without doing the encoding...

And what we can't do is to add bugs.

Sorry

@blaghed
Copy link
Author

blaghed commented Dec 5, 2023

Yes, fixing a bug by adding another is not the intent here :)

But, if I understand right, your opinion here is that nothing at all should be done for this, right?

@jorgerod
Copy link
Contributor

jorgerod commented Dec 5, 2023

Correct. In my opinion, if resttemplate is configured properly, I don't think there is any bug.

I am very sorry

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

Successfully merging this pull request may close these issues.

None yet

4 participants