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

Provide the ability to specify the Content Type of a non-named Multipart Part #1888

Open
adrianhj opened this issue Apr 17, 2023 · 3 comments

Comments

@adrianhj
Copy link
Contributor

adrianhj commented Apr 17, 2023

Is your feature request related to a problem? Please describe.

When utilising @RequestPart on a Controller argument in Spring for working with multipart/form-data requests consisting of e.g. a file part and a JSON part—and expecting the JSON part to be correctly deserialised to a POJO—it relies on the Content-Type of the request part to find and apply the relevant HttpMessagingConverter; defaulting to application/octet-stream if none was provided.

From the client perspective these parts are not files as such and do not have file names. MultipartBodyBuilder.part(String name, Object part, MediaType contentType) for example allows specifying such a part with a relevant content type when working with the WebClient.

Describe the solution you'd like

Provide the ability to specify the content type of a multipart part without a filename within a contract so as to generate valid requests.

Describe alternatives you've considered

Utilising the named(name, content, contentType) method, however this puts the burden onto any integrating party to provide a dummy name to satisfy the generated WireMock mappings as it is not optional whilst not being required by the endpoint.

Additional context

REST Assured seems to have various flavours of multiPart, some providing the ability to specify a multipart part with a relevant content type as desired which is probably of interest as compared to the currently used param method which seems more intended for form params.

In the process of attempting to provide a part(content, contentType) method or the like, but figured I'd open for discussion if this is something that has been covered previously or whether I have misunderstood any aspect of verifying a JSON encoded @RequestPart via Spring Cloud Contract.

@marcingrzejszczak
Copy link
Contributor

Do you have a small Java-Maven sample that you could show with such client - server communication?

@adrianhj
Copy link
Contributor Author

adrianhj commented Apr 17, 2023

@marcingrzejszczak I have built a small example in https://github.com/adrianhj/spring-cloud-contract-1888, hopefully this helps showcase the gap (issue?) utilising Spring.

Areas of interest:

For the RestTemplate backed test, I have upped the logging to easily see what is sent over the wire:

o.s.web.client.RestTemplate              : HTTP POST http://localhost:57620/example
o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
o.s.web.client.RestTemplate              : Writing [{file=[class path resource [contracts/file.txt]], metadata=[Metadata[value=example]]}] as "multipart/form-data"
org.apache.hc.client5.http.headers       : http-outgoing-0 >> POST /example HTTP/1.1
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Accept: application/json, application/*+json
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Content-Type: multipart/form-data;boundary=nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Accept-Encoding: gzip, x-gzip, deflate
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Content-Length: 345
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Host: localhost:57620
org.apache.hc.client5.http.headers       : http-outgoing-0 >> Connection: keep-alive
org.apache.hc.client5.http.headers       : http-outgoing-0 >> User-Agent: Apache-HttpClient/5.1.4 (Java/17.0.6)
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "POST /example HTTP/1.1[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Accept: application/json, application/*+json[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Type: multipart/form-data;boundary=nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Accept-Encoding: gzip, x-gzip, deflate[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Length: 345[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Host: localhost:57620[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Connection: keep-alive[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "User-Agent: Apache-HttpClient/5.1.4 (Java/17.0.6)[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "--nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Disposition: form-data; name="file"; filename="file.txt"[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Type: text/plain[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Length: 4[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Test[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "--nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Disposition: form-data; name="metadata"[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Type: application/json[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "{"value":"example"}[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "--nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX--[\r][\n]"

The area of particular interest is the metadata part for which the structure is currently impossible to achieve as far as I can tell and the purpose of this feature request:

org.apache.hc.client5.http.wire          : http-outgoing-0 >> "--nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Disposition: form-data; name="metadata"[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "Content-Type: application/json[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "{"value":"example"}[\r][\n]"
org.apache.hc.client5.http.wire          : http-outgoing-0 >> "--nVCcViZpN810-A5mTBQ7AuGDfftezMFj_jPX--[\r][\n]"

@adrianhj
Copy link
Contributor Author

I started some work on #1929 at the time but it fell dormant.

I have opened it for commentary around whether the approach would be of interest to be progressed.

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

3 participants