Fix conflation of () which serializes to the JSON value null and a return that produces no content#198
Fix conflation of () which serializes to the JSON value null and a return that produces no content#198
null and a return that produces no content#198Conversation
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. |
| .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.
@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.
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.
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) producednulland 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().