-
Notifications
You must be signed in to change notification settings - Fork 79
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
Fix conflation of () which serializes to the JSON value null
and a return that produces no content
#198
Conversation
return that actually produces no content. For the latter, we introduce, a new type `EMPTY` which has a custom JsonSchema implementation that evaluates to the empty schema (i.e. nothing can match it).
Dang, things for digging into this. How would you summarize the impact for Dropshot users? Is it that: if you previously were returning And were we previously returning a 4-byte I found it surprising that |
Yeah, I had planned an entry to the changelog ;-) If your endpoint returns {
"title": "Null",
"type": "string",
"enum": [
null
]
} While this schema is a little awkward, OpenAPI 3.0.x doesn't provide a representation for a null type (though it does provide a representation of a type that may be
No! We special-cased those types: impl From<HttpResponseUpdatedNoContent> for HttpHandlerResult {
fn from(_: HttpResponseUpdatedNoContent) -> HttpHandlerResult {
Ok(Response::builder()
.status(HttpResponseUpdatedNoContent::STATUS_CODE)
.body(Body::empty())?)
}
} Thinking this through more, I added some code to handle the case where a user specifies, say, fn for_object(body_object: &Self::Body) -> HttpHandlerResult {
let response = Response::builder().status(Self::STATUS_CODE);
if <dyn std::any::Any>::is::<Empty>(body_object) {
Ok(response.body(Body::empty())?)
} else {
let serialized = serde_json::to_string(&body_object)
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
Ok(response
.header(http::header::CONTENT_TYPE, CONTENT_TYPE_JSON)
.body(serialized.into())?)
}
} Thoughts?
Picayune as it is, I think that's right. |
dropshot/src/handler.rs
Outdated
.status(Self::STATUS_CODE) | ||
.header(http::header::CONTENT_TYPE, CONTENT_TYPE_JSON) | ||
.body(serialized.into())?) | ||
let response = Response::builder().status(Self::STATUS_CODE); |
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.
@davepacheco I'd love to get your eyes on this in particular; I don't love it, but I think maybe it's better than not doing it. In particular, I think casting is icky, but I don't know how else to see if we've got an Empty type. Alternatively, perhaps we don't make Empty
pub.
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 kind of like not making Empty
pub and saying that you should use a different HttpResponseOkEmpty
...but I hope someone will push back if that seems excessively pedantic. I'd even consider renaming HttpResponseOk
to HttpResponseOkBody
, though that does seem pretty annoying for existing consumers.
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.
Well it turns out that Empty
must be pub because it's used as the Body
type param for some pub
response types. But I made it #[doc(hidden)]
to discourage its use... which would otherwise fail at runtime. I didn't rename the type because--as you said--it would be annoying to consumers.
We've used
()
as a sentinel value that means that an endpoint produces no response body. In fact, however,()
serializes to the JSON valuenull
. We ran into this in progenitor because some endpoints (inadvertently) producednull
and would therefore deserialize into()
, but the endpoints that produce no data can't be properly deserialized into()
because there is no data.This PR distinguishes between endpoints that return
()
and those that actually return nothing.I could easily be persuaded that a better way to address this disparity would be simply to special case response bodies that are simply
()
and have them return zero-length response bodies. This may, however, be non-intuitive (in a different way that the proposed solution is non-intuitive, perhaps), and may be hard to reliably in all cases such as if one had a newtype alias for()
.