Closed mlaota closed 1 week ago
I would vote for option 2. I'd rather not introduce new extensions. I believe not many people know about them and that you can implement your own.
Regarding error types, why do we need new error types for that PayloadError
doesn't cover? The errors that you're creating are pretty much the same as the variants in that enum.
I would vote for option 2. I'd rather not introduce new extensions. I believe not many people know about them and that you can implement your own.
Sounds good.
Regarding error types, why do we need new error types for that PayloadError doesn't cover?
Good question -- the goal is that users should only be forced to handle errors that are thrown by their intended content-type deserializer. PayloadError
works great with the current behavior in the case where the deserializer is selected at run-time, since it's really the client who chooses it based on the content-type header.
If we flip that decision to the server with this new API, we can strongly guarantee that only related error types will be thrown by using a more specific error type; so, when I'm calling .json()
, I no longer need to consider the possibility of a PayloadError::WwwFormUrlEncoded
error, because the compiler guarantees that won't happen; whereas, if we reuse PayloadError
, I still have to write a handler for other content types even if I'm explicitly using the json()
method, and the addition of any other content type would be a breaking change (e.g. if we add PayloadError::TextHtml
, any user who fully matches the PayloadError
type now has to add one more arm).
The errors that you're creating are pretty much the same as the variants in that enum.
I'm open to feedback on this. This was basically my thought process:
That makes sense. I have no naming preference for the error types, tbh. Feel free to open a PR with these changes and we can discuss it further in code review.
PR above is ready for review.
(Sorry for the mention spam there, that was because of git amends + pushes to work on different laptops lol)
This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one.
Summary
This RFC proposes ergonomic improvements to payload deserialization for HTTP usecases.
Motivation
The
RequestPayloadExt
trait is used inlambda_http
to simplify extracting aRequest
's body into a user-provided struct. The current version of theRequestPayloadExt::payload
has the following docstring:The interpretation of how
payload
should behave when the content-type header is missing/unsupported is somewhat ambiguous. For example, there's no mention of what happens when the content-type header is missing. According to RFC 9110: HTTP Semantics,so, it's not unreasonable to assume that a generic
payload
function on an HTTPRequest
struct would also do a best-effort estimation of the content-type. The signature ofpayload
asks to handle multiplePayloadError
variants for different content types which may reinforce that assumption.The actual behavior is that when the content-type header is either missing or unsupported, the implementation assumes there is no content and returns
Ok(None)
; however, the docstring only calls outOk(None)
being returned "if no body is provided" which can lead to frustration when trying to figure out why the payload is not being recognized.The documentation needs an update to disambiguate the scenario where the content-type is missing or unsupported. Additionally, I've written a proposal for API ergonomics improvements when handling different content types below.
Proposal: Intent-Driven API Additions
We can use this as an opportunity to introduce a fix via intent-driven additions. For example, we can add
json()
andx_www_form_urlencoded()
methods to the Request that make it clear what the expected format should be:Option 1. (Preferred) Introduce a new extension trait,
RequestPayloadIntentExt
, with initial implementationsThe extension trait would have the following interface:
And initial implementations would go here:
Advantages
json()
andx_www_form_urlencoded()
.Drawbacks
RequestPayloadExt
: users may be confused why the two implementations exist, and surprised when they behave differentlyOption 2: Extend RequestPayloadExt trait with methods
json()
andx_www_form_urlencoded()
The trait could be modified as follows:
Advantages
payload
can reuse the implementations forjson
andxx_www_form_urlencoded
and share in improvements.Drawbacks
json
andx_www_form_urlencoded
.self.payload
to mimic the previous behavior.PayloadError::Json
andJsonPayloadError
coexisting in the same module.payload
and new methodsAlternative: Try all content types deserializers before returning None
Since both current (and I'm assuming future) content-type deserialization implementations leverage Serde's
Deserialize
trait as inputs, we can try both deserializers on the input when the content-type header is missing. For example, we can start with JSON, catch errors, then try x-www-form-urlencoded, etc...Advantages
Drawbacks
None
when the "content-type" header is empty, they may start handling requests that they expected to reject when the header is missing; consequently, they may be surprised at the new behavior and/or be forced to implement a different mechanism for denying requests without a content-type header.