Current behavior
try_as<T>() ignores the HTTP status: a 4xx/5xx response completes with ec == success and the converted body.
error_for_status().try_as<T>() reports a status error but skips the body entirely, returning a default-constructed T.
Neither form can return both the status error and the body — which is the whole point of the
[ec, r] interface. Error responses often carry useful diagnostics (e.g. RFC 7807 problem
details), and reading them currently requires dropping down to send() plus manual status checks.
Proposed behavior
try_as<T>() always attempts to read and convert the body. ec reports the most significant
error according to this precedence:
- transport errors (special case: discarded if they occur after a complete response has been
parsed, e.g. a rude disconnect)
- HTTP protocol errors
- HTTP status errors (4xx/5xx)
- body conversion errors
The result holds the converted value, or a default-constructed T when conversion fails or the
body is empty. Whether partial data is kept on error remains the per-type policy of
tag_invoke(body_to_tag<T>, ...) (e.g. std::string and file bodies keep the partial result).
For auto [ec, json] = co_await client.get(url).try_as<json::value>(); on a 4xx/5xx response:
| scenario |
ec |
json |
| body is valid JSON |
status error |
parsed value |
| body is HTML/plain text (parse fails) |
status error |
{} |
| transport/protocol error while reading body |
that error |
{} |
On a 2xx response, a conversion error is the first error in the sequence and is reported as-is.
Conversion does not require a matching Content-Type; e.g. try_as<json::value>() attempts to
parse the body regardless of what the server advertises. Many servers mislabel error bodies, and
the conversion error already conveys the mismatch.
Rationale: a status error must not be masked by a conversion error. Error bodies frequently come
from CDN/infrastructure layers as HTML or plain text (e.g. Google APIs returning an HTML page on
429), so a JSON parse error would hide the actual cause of failure.
Remove error_for_status()
With status errors flowing through ec automatically, error_for_status() becomes redundant for
the non-throwing path — any meaningful caller inspects the status anyway. For the throwing path,
as<T>() now treats 4xx/5xx as errors unconditionally; realistically nobody writes
co_await client.post(url).as<T>() expecting a 4xx/5xx to succeed.
(Design settled in a discussion with Peter Dimov.)
Current behavior
try_as<T>()ignores the HTTP status: a 4xx/5xx response completes withec == successand the converted body.error_for_status().try_as<T>()reports a status error but skips the body entirely, returning a default-constructedT.Neither form can return both the status error and the body — which is the whole point of the
[ec, r]interface. Error responses often carry useful diagnostics (e.g. RFC 7807 problemdetails), and reading them currently requires dropping down to
send()plus manual status checks.Proposed behavior
try_as<T>()always attempts to read and convert the body.ecreports the most significanterror according to this precedence:
parsed, e.g. a rude disconnect)
The result holds the converted value, or a default-constructed
Twhen conversion fails or thebody is empty. Whether partial data is kept on error remains the per-type policy of
tag_invoke(body_to_tag<T>, ...)(e.g.std::stringand file bodies keep the partial result).For
auto [ec, json] = co_await client.get(url).try_as<json::value>();on a 4xx/5xx response:{}{}On a 2xx response, a conversion error is the first error in the sequence and is reported as-is.
Conversion does not require a matching
Content-Type; e.g.try_as<json::value>()attempts toparse the body regardless of what the server advertises. Many servers mislabel error bodies, and
the conversion error already conveys the mismatch.
Rationale: a status error must not be masked by a conversion error. Error bodies frequently come
from CDN/infrastructure layers as HTML or plain text (e.g. Google APIs returning an HTML page on
429), so a JSON parse error would hide the actual cause of failure.
Remove error_for_status()
With status errors flowing through
ecautomatically,error_for_status()becomes redundant forthe non-throwing path — any meaningful caller inspects the status anyway. For the throwing path,
as<T>()now treats 4xx/5xx as errors unconditionally; realistically nobody writesco_await client.post(url).as<T>()expecting a 4xx/5xx to succeed.try_as<T>(): read/convert the body unconditionally, apply the error precedence aboveas<T>(): throw on 4xx/5xx by defaulterror_for_status()from the request builder(Design settled in a discussion with Peter Dimov.)