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

Fail when there's an unexpected response body #3095

Merged
merged 10 commits into from
Aug 24, 2023
Merged

Conversation

kciesielski
Copy link
Member

Copy link

@gerryfletch gerryfletch left a comment

Choose a reason for hiding this comment

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

Awesome, greatly appreciate you taking the time to add this in :)

@kciesielski kciesielski requested a review from adamw August 8, 2023 13:08
@kciesielski kciesielski marked this pull request as ready for review August 8, 2023 13:09
case Some(bodyFromHeaders) => ServerResponse(statusCode, headers, Some(bodyFromHeaders(Headers(headers))), Some(output)).unit
case None => ServerResponse(statusCode, headers, None: Option[B], Some(output)).unit
(statusCode, outputValues.body) match {
case (StatusCode.NoContent, Some(_)) => monad.error(new IllegalStateException("Unexpected response body when status code == NoContent (204)"))
Copy link
Member

Choose a reason for hiding this comment

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

maybe there are more status codes which prohibit a body? https://stackoverflow.com/questions/8628725/comprehensive-list-of-http-status-codes-that-dont-include-a-response-body has some candidates, but it would be good to verify in the spec

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like 304 and 1xx are bodyless by specification as well. I don't think we need to account for 1xx, but for 304 it might be worth it. As far as I understand, it's a status typically provided by caching mechanisms and probably shouldn't be set manually by Tapir users, but let's not underestimate their creativity :)

@adamw
Copy link
Member

adamw commented Aug 17, 2023

We could also extend EndpointVerifier to check that an endpoint doesn't specify a fixed 204 status code + some body

@kciesielski kciesielski changed the title Fail when there's a response body with 204 status code Fail when there's an unexpected response body Aug 17, 2023
@kciesielski kciesielski requested a review from adamw August 21, 2023 11:05
case Some(bodyFromHeaders) => ServerResponse(statusCode, headers, Some(bodyFromHeaders(Headers(headers))), Some(output)).unit
case None => ServerResponse(statusCode, headers, None: Option[B], Some(output)).unit
(statusCode, outputValues.body) match {
case (StatusCode.NoContent | StatusCode.NotModified, Some(_)) => monad.error(new IllegalStateException(s"Unexpected response body when status code == $statusCode"))
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it would be better to say: The status code XYZ doesn't allow a body

Also ... what if the response body is defined as an Option? Or if the body is returned as an empty string "" - shouldn't we special case on this?

Copy link
Member Author

Choose a reason for hiding this comment

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

That would be hard to check in this general function. The body is of type B which is backend-dependent (like akka.http.scaladsl.model.HttpEntity).

Copy link
Member

Choose a reason for hiding this comment

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

Ok, forget it then :)

val e2 = endpoint.in("endpoint2_Ok").out(stringBody).out(statusCode(StatusCode.BadRequest))
val e3 = endpoint.in("endpoint3_Err").out(stringBody).out(statusCode(StatusCode.NotModified))
val e4 = endpoint.in("endpoint4_ok").out(emptyOutputAs(NoContent)).out(statusCode(StatusCode.NoContent))
val e5 = endpoint
Copy link
Member

Choose a reason for hiding this comment

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

Would the checker pass for an endpoint such as:

val e6 = endpoint
      .in("endpoint6_ok")
       .errorOut(
         sttp.tapir.oneOf[ErrorInfo](
           oneOfVariant(statusCode(StatusCode.NotFound).and(jsonBody[NotFound])),
           oneOfVariant(statusCode(StatusCode.NoContent).and(emptyOutputAs[NoContent]))
         )
       )

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea to check this, I have added a test.

@adamw adamw merged commit a402fc9 into master Aug 24, 2023
@adamw
Copy link
Member

adamw commented Aug 24, 2023

Thanks :)

@mergify mergify bot deleted the fix/nocontent-body branch August 24, 2023 19:18
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.

3 participants