-
Notifications
You must be signed in to change notification settings - Fork 38
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
Conversation
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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.
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/HttpResponseImpl.scala
Outdated
Show resolved
Hide resolved
sdk/java-sdk-spring/src/it/java/com/example/wiring/actions/echo/ActionWithHttpResponse.java
Outdated
Show resolved
Hide resolved
505a9d0
to
6e07ef9
Compare
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
@@ -39,6 +39,7 @@ | |||
import com.google.protobuf.UnsafeByteOperations; | |||
import kalix.javasdk.annotations.Migration; | |||
import kalix.javasdk.impl.ByteStringEncoding; | |||
import org.jetbrains.annotations.NotNull; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jetbrains dependency?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 betext/plain
- when user passes
Object
it will beapplication/json
- when user passes
bytes[]
it will beapplication/octet-stream
The NoContent
one can default to application/octet-stream
which is not wrong since we are sending byte[0]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/HttpResponseImpl.scala
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/scala/kalix/javasdk/impl/HttpResponseImpl.scala
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/HttpResponse.java
Outdated
Show resolved
Hide resolved
@@ -39,6 +39,7 @@ | |||
import com.google.protobuf.UnsafeByteOperations; | |||
import kalix.javasdk.annotations.Migration; | |||
import kalix.javasdk.impl.ByteStringEncoding; | |||
import org.jetbrains.annotations.NotNull; |
There was a problem hiding this comment.
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 betext/plain
- when user passes
Object
it will beapplication/json
- when user passes
bytes[]
it will beapplication/octet-stream
The NoContent
one can default to application/octet-stream
which is not wrong since we are sending byte[0]
.
public <T> T bodyAs(Class<T> clz) throws IOException { | ||
return JsonSupport.parseBytes(body, clz); | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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. 🤷
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
68f2b1d
to
6247e7a
Compare
There was a problem hiding this 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.
public HttpResponse withStatusCode(StatusCode.Success statusCode) { | ||
return new HttpResponse(statusCode, contentType, body); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
if (text == null) throw new IllegalArgumentException("text must not be null"); | ||
return new HttpResponse(StatusCode.Success.OK, "text/plain", text.getBytes(UTF_8)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
HttpResponse | ||
.ok(httpBody.getData.toByteArray) | ||
.withStatusCode(StatusCode.Success.from(statusCode)) | ||
.withContentType(contentType) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
e910a36
to
a4058e3
Compare
There was a problem hiding this 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.
References