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

Support Multipart @FormParam on EntityPart #41204

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

ollelogdahl
Copy link

@ollelogdahl ollelogdahl commented Jun 14, 2024

This is a fix of issue #41186

Previously, @FormParam was not supported on EntityPart parameters in resources, and would cause a runtime error stating that a converter is missing. This was not compliant to the Jakarta RESTful Web Services 3.1 spec. Now, this is fixed by handling EntityPart as a 'multipart type' during conversion and adding EntityPart support to MultipartFormParamExtractor.

Two test cases have been added to ensure correctness.

@ollelogdahl
Copy link
Author

ollelogdahl commented Jun 14, 2024

I'll just add it down here, but this implementation is NOT complete. Most of it looks correct, but we should really have more complete tests and obviously implement the getContent with type alternatives. I added a 'dummy' EntityPart implementation for now. I think we could include the one in org.jboss.resteasy.plugins.providers.multipart, but I'm not sure if that is for the old quarkus rest. I would appreciate some feedback on how we should do this. I know that Response hello(List<EntityPart> parts) works right now (using List<EntityPart> as the body parameter), which means there is some way internally that EntityParts are built up, but I couldn't find it. @FroMage

Copy link
Member

@FroMage FroMage left a comment

Choose a reason for hiding this comment

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

Wow, this looks great. Impressive in such a short time :)

I've added a few comments, but you're going in the right direction.

Looking at the spec, it appears that we need the following to fully support EntityPart:

  • @FormParam("name") EntityPart param and @FormParam("name") List<EntityPart> param, which you've done
  • List<EntityPart> as a body parameter. I suppose that one catches all parts, so it's the equivalent of our @RestForm(FileUpload.ALL) List<FileUpload> or MultipartFormDataInput
  • Implement RuntimeDelegateImpl.createEntityPartBuilder and the builder type
  • Probably the client side support too? The spec doesn't say much about that, perhaps the TCK does, but we don't run it, unfortunately.

Additionally, on our end, we'll need to:

  • Update the docs to mention EntityPart in multipart support
  • Ultimately, we'll need to figure out if and how to unify our multipart support with the spec's.

I didn't see anything in the spec about returning EntityPart to return multipart from the endpoint. Or about the client support for it.

if (value.isFileItem()) {
this.content = new FileInputStream(value.getFileItem().getFile().toFile());
} else {
this.content = new ByteArrayInputStream(value.getValue().getBytes(Charset.defaultCharset()));
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure what encoding the byte array should have, but I suspect not the default charset. First, because we prefer utf-8 everywhere, and second because it's possible that anybody reading the InputStream will use the part's encoding as set in headers or media type, which would be different to the default charset.

Also, perhaps the InputStream should be build lazily in getContent? Otherwise if a user closes it, doing two calls to getContent will fail.


@Override
public InputStream getContent() {
return this.content;
Copy link
Member

Choose a reason for hiding this comment

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

Probably lazily build the InputStream here based on the value.

@FroMage
Copy link
Member

FroMage commented Jun 14, 2024

I know that Response hello(List parts) works right now (using List as the body parameter), which means there is some way internally that EntityParts are built up, but I couldn't find it.

Are you sure that works? Withour @FormParam? That's not what I expect and I wonder why it works.

This comment has been minimized.

@ollelogdahl
Copy link
Author

Are you sure that works? Without @FormParam? That's not what I expect and I wonder why it works.

Yes I'm pretty sure; we can add that as a test-case as well. I don't really know how it works though.

Thank you for the quick feedback! I will update the PR according to your comments and take a look at the builder. I agree that client-side is a must long-term, but it is probably easier to do that at a later stage?

@FroMage
Copy link
Member

FroMage commented Jun 14, 2024

it is probably easier to do that at a later stage?

Sure, it can be done later.

@ollelogdahl
Copy link
Author

ollelogdahl commented Jun 18, 2024

I have updated my PR according to your comments. I am not sure if everything is fully correct but it's getting there. Yes, I looked back and saw that no, List<EntityPart> as a body-parameter is not actually supported in quarkus-rest. I am thinking that I'll fix that next.

Copy link
Member

@FroMage FroMage left a comment

Choose a reason for hiding this comment

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

Nice, good progress :)

Copy link

quarkus-bot bot commented Jun 18, 2024

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit dfc6638.

Failing Jobs

Status Name Step Failures Logs Raw logs Build scan
Native Tests - HTTP Build Failures Logs Raw logs 🔍

Full information is available in the Build summary check run.
You can consult the Develocity build scans.

Failures

⚙️ Native Tests - HTTP #

- Failing: integration-tests/rest-client-reactive-multipart 

📦 integration-tests/rest-client-reactive-multipart

io.quarkus.it.rest.client.multipart.MultipartResourceIT.shouldHandleEntityPartMessage - History - More details - Source on GitHub

java.lang.AssertionError: 
1 expectation failed.
Expected status code <200> but was <400>.

	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:73)
	at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:108)

Flaky tests - Develocity

⚙️ JVM Tests - JDK 21

📦 extensions/smallrye-reactive-messaging-kafka/deployment

io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase.sseStream - History

  • Assertion condition defined as a Lambda expression in io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase Expecting size of: [] to be greater than or equal to 2 but was 0 within 10 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: 
Assertion condition defined as a Lambda expression in io.quarkus.smallrye.reactivemessaging.kafka.deployment.dev.KafkaDevServicesDevModeTestCase 
Expecting size of:
  []
to be greater than or equal to 2 but was 0 within 10 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)

📦 integration-tests/grpc-hibernate

com.example.grpc.hibernate.BlockingRawTest.shouldAdd - History

  • Condition with Lambda expression in com.example.grpc.hibernate.BlockingRawTestBase was not fulfilled within 30 seconds. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in com.example.grpc.hibernate.BlockingRawTestBase was not fulfilled within 30 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1006)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:975)
	at com.example.grpc.hibernate.BlockingRawTestBase.shouldAdd(BlockingRawTestBase.java:59)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)

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

Successfully merging this pull request may close these issues.

2 participants