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

feat: http response as endpoint return type #1965

Merged
merged 1 commit into from
Jan 18, 2024

Conversation

aludwiko
Copy link
Contributor

References

Copy link
Member

@octonato octonato left a comment

Choose a reason for hiding this comment

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

Looking good.

I think we will need some more iterations to explore some API ergonomics, but this is moving now into an interesting direction.

@aludwiko aludwiko force-pushed the http-response-highlevel-api branch from 505a9d0 to 6e07ef9 Compare January 12, 2024 10:00
@@ -39,6 +39,7 @@
import com.google.protobuf.UnsafeByteOperations;
import kalix.javasdk.annotations.Migration;
import kalix.javasdk.impl.ByteStringEncoding;
import org.jetbrains.annotations.NotNull;
Copy link
Member

Choose a reason for hiding this comment

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

jetbrains dependency?

Copy link
Contributor Author

@aludwiko aludwiko Jan 15, 2024

Choose a reason for hiding this comment

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

removed, and content type is once again optional.

Copy link
Member

Choose a reason for hiding this comment

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

Using optional will be annoying for Scala and for users as well.

Since the API is controlling the payload already, we can always set the content-type behind the scene.

  • when user passes String it will be text/plain
  • when user passes Object it will be application/json
  • when user passes bytes[] it will be application/octet-stream

The NoContent one can default to application/octet-stream which is not wrong since we are sending byte[0].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

Copy link
Member

Choose a reason for hiding this comment

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

Jetbrains dependency still being imported here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

@@ -39,6 +39,7 @@
import com.google.protobuf.UnsafeByteOperations;
import kalix.javasdk.annotations.Migration;
import kalix.javasdk.impl.ByteStringEncoding;
import org.jetbrains.annotations.NotNull;
Copy link
Member

Choose a reason for hiding this comment

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

Using optional will be annoying for Scala and for users as well.

Since the API is controlling the payload already, we can always set the content-type behind the scene.

  • when user passes String it will be text/plain
  • when user passes Object it will be application/json
  • when user passes bytes[] it will be application/octet-stream

The NoContent one can default to application/octet-stream which is not wrong since we are sending byte[0].

Comment on lines 121 to 123
public <T> T bodyAs(Class<T> clz) throws IOException {
return JsonSupport.parseBytes(body, clz);
}
Copy link
Member

@octonato octonato Jan 15, 2024

Choose a reason for hiding this comment

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

I realise now that this method can only be useful if added to the HttpResponse.

When we receive a response from the ComponentClient, we will take the internal http response from the WebClient and map it to HttpResponse, no?

Actually, it seems to me that the only public type should be HttpResponse.

So, in that case, it must communicate in its name that it is only applicable with payload is json. Something like jsonBodyAs(Class<T> clz)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having HttpResponse as the only public class would introduce another problems, for instance we allow to change the content-type only in case of ByteContentResponse. If we move this method to the interface, then other implementations will throw an exception. But I agree that in fact you will not be able to use this method, because I'm parsing the response always to ByteContentReponse.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe it's time to move one step back and remove all the specific implementations and have only a single class with factory methods? and then we could have jsonBodyAs(Class<T> clz)?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I was having the same thought. We can keep the specific methods, but one could always change the content-type and status code later. Not a good idea, but it will make things much simpler.

So, for example, if I really want to mess-up, I can do HttpResponse.ok(Foo). That will return a 200 with json. Then HttpResponse.ok(Foo).withContentType(whatever).withStatusCode(NotFound).

I would be messing up, but then we can argue that the user chose to do so. 🤷

Copy link
Member

Choose a reason for hiding this comment

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

An alternative could be that we only offer a method to change the content-type, but not the status code nor the body. This is determined by the helper method ok(T), ok(byte[]), noContent(), notFound(), etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would have more faith in our users. This is low-level api, they shouldn't use it for normal cases, we just want to support very specific cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can check the implementation now.

@github-actions github-actions bot added the Documentation Improvements or additions to the documentation label Jan 15, 2024
@aludwiko aludwiko force-pushed the http-response-highlevel-api branch from 68f2b1d to 6247e7a Compare January 15, 2024 16:33
@github-actions github-actions bot removed the Documentation Improvements or additions to the documentation label Jan 15, 2024
Copy link
Member

@octonato octonato left a comment

Choose a reason for hiding this comment

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

Taking good shape.

I left a few more comments. But I think it will be my last ones. 😄

Before merging we should exercise it a bit to ensure we have got a good API.

Comment on lines 84 to 127
public HttpResponse withStatusCode(StatusCode.Success statusCode) {
return new HttpResponse(statusCode, contentType, body);
}
Copy link
Member

Choose a reason for hiding this comment

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

I still think this is not ok. If we are providing methods that already define the status code, we should not provide this method.

It's quite strange to have something like HttpResponse.ok(..).withStatusCode(somethingElse).
We will be forcing people to first build a 200 and then switch to another code.

I agree that it will be massive to have specific methods for each of the 10 success status code we support. Also because most of them are not that common to justify helper methods.

So maybe, alongside ok(), noContent(), created() and accepted(), we can provide HttpResponse.for(StatusCode.Success code, String contentType, byte[] payload). That last would be the more low-level power user one.

Then the options would be:

  • either use one of the most common helper methods, eg: ok(), created(), etc
  • or build a HttpResponse fully from scratch by passing each piece: code, content-type and bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, it was like that at the beginning, I will revert it

}

public static HttpResponse ok(Object object) {
if (object == null) throw new IllegalArgumentException("object must not be null");
Copy link
Member

Choose a reason for hiding this comment

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

isn't null a valid json object?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would answer the same as above.

Copy link
Member

Choose a reason for hiding this comment

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

I agree for byte[], but not for Object. Json spec accept defines null as a valid value (or should I say non-value?).

Although we might find it a bad idea, it might be something that we should allow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would you like to serialize it and then deserialize? empty byte array = null object? Also, we can always remove this check later.

Copy link
Member

Choose a reason for hiding this comment

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

Sure, we can add it later.

But to answer your question, when we build the HttpBody, we simply set the payload. When we read it back, we fallback to null.

Anyway, let's leave it for later. Better to start restrictive and relax the rules then the other way around.

Comment on lines +65 to +95
if (text == null) throw new IllegalArgumentException("text must not be null");
return new HttpResponse(StatusCode.Success.OK, "text/plain", text.getBytes(UTF_8));
Copy link
Member

Choose a reason for hiding this comment

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

would it be a good idea to send an empty byte[] instead of throwing exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure, null might indicate an implementation problem that an empty array will hide.

Comment on lines 146 to 149
HttpResponse
.ok(httpBody.getData.toByteArray)
.withStatusCode(StatusCode.Success.from(statusCode))
.withContentType(contentType)
Copy link
Member

Choose a reason for hiding this comment

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

Related to my other comment. If we have a method with all three param, we can build it in one shot.

if (result.typeUrl == JsonSupport.KALIX_JSON + "object") {
val typeUrl = messageCodec.typeUrlFor(expectedInputClass)
messageCodec.decodeMessage(result.copy(typeUrl = typeUrl))
} else if (result.typeUrl == "type.googleapis.com/google.api.HttpBody") {
val httpBody = HttpBody.parseFrom(result.value.newCodedInput())
httpBody.getExtensionsList.asScala.find(_.getTypeUrl == "Status-Code") match {
Copy link
Member

Choose a reason for hiding this comment

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

Here we can use HttpBodyStatusCodeExtensionTypeUrl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately not, it's from the java-sdk-spring module. Maybe we should prefix the constant somehow to improve the text-based searching?

Copy link
Member

Choose a reason for hiding this comment

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

I think we have a place in the proto sdk where we have some other constants defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I can flip the dependency, and now I think it's fine.

@aludwiko aludwiko requested a review from octonato January 17, 2024 08:50
Copy link
Member

Choose a reason for hiding this comment

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

I think you removed by accident the jsonBodyAs method, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's back now

updating tests

HttpBody to HttpResponse

more null safety

missing header

fmt

fixing workflows tests

fmt

Apply suggestions from code review

Co-authored-by: Renato Cavalcanti <[email protected]>

specig HttpResponse types

addressing PR comments

fmt

updates after PR review

removing dedicate HttpResponse subclasses

Apply suggestions from code review

Co-authored-by: Renato Cavalcanti <[email protected]>

PR comments

constant comment

test compilation

adressing PR comments

no content fixed

missing docs

docs typo

Update sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java

Co-authored-by: Renato Cavalcanti <[email protected]>

missing method for parsing bytes to json

# Conflicts:
#	sdk/java-sdk-spring/src/it/java/com/example/wiring/SpringSdkIntegrationTest.java
@aludwiko aludwiko force-pushed the http-response-highlevel-api branch from e910a36 to a4058e3 Compare January 18, 2024 13:39
@aludwiko aludwiko requested a review from octonato January 18, 2024 13:50
Copy link
Member

@octonato octonato left a comment

Choose a reason for hiding this comment

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

Let's ship it!

Thanks for the patience with all the change requests.

@octonato octonato merged commit aac3509 into main Jan 18, 2024
68 checks passed
@octonato octonato deleted the http-response-highlevel-api branch January 18, 2024 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants