We collect these functions in a separate module, rather than exporting them
from Network.GRPC.Spec, because while the functions in Network.GRPC.Specmay be needed in some user code (albeit rarely), the serialization
functions from this module really should only be needed in gRPC
implementations such as grapesy.
Parsing a pushback cannot fail; the spec mandates:
If the value for pushback is negative or unparseble, then it will be seen
as the server asking the client not to retry at all.
We therefore only require Monad m, not MonadError m (having the Monad
constraint at all keeps the type signature consistent with other parsing
functions).
See also Network.GRPC.Spec.parseRequestHeaders versus
'Network.GRPC.Spec.parseRequestHeaders' for a similar pair of functions.
See ProperTrailers' for a discussion of why ProperTrailers' is not
parameterized (unlike ResponseHeaders' and
RequestHeaders').
Under normal circumstances this should be Just .chooseContentType.
In some cases, however, the content-type might not be known. For
example, when a request comes in for an unknown method, the gRPC server
is supposed to respond with a Trailers-Only message, with an
UNIMPLEMENTED error code. Frustratingly, Trailers-Only requires a
Content-Type header, even though there is no content. This
Content-Type header normally indicates the serialization format (e.g.,
application/grpc+proto), but this format depends on the specific
method, which was not found!
To resolve this catch-22, this function is allowed to return Nothing,
in which case the Content-Type we will use application/grpc, with no
format specifier. Fortunately, this is allowed by the spec.
gRPC servers are supposed to respond with HTTP status 200 OK no matter
whether the call was successful or not; if not successful, the information
about the failure should be reported using grpc-status and related headers
(grpc-message, grpc-status-details-bin).
The gRPC spec mandates that if we get a non-200 status from a broken
deployment, we synthesize a gRPC exception with an appropriate status and
status message. The spec itself does not provide any guidance on what such an
appropriate status would look like, but the official gRPC repo does provide a
partial mapping between HTTP status codes and gRPC status codes at
https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
This is the mapping we implement here.
The presence of duplicate headers makes this a bit subtle. Let's consider an
example. Suppose we have two duplicate headers
foo-bin: YWJj -- encoding of "abc"
foo-bin: ZGVm -- encoding of "def"
The spec says
Custom-Metadata header order is not guaranteed to be preserved except for
values with duplicate header names. Duplicate header names may have their
values joined with "," as the delimiter and be considered semantically
equivalent.
We will do the decoding of both headers prior to joining duplicate headers,
and so the value we will reconstruct for foo-bin is "abc,def".
However, suppose we deal with a (non-compliant) peer which is unaware of
binary headers and has applied the joining rule without decoding:
foo-bin: YWJj,ZGVm
The spec is a bit vague about this case, saying only:
Implementations must split Binary-Headers on "," before decoding the
Base64-encoded values.
Here we assume that this case must be treated the same way as if the headers
had been decoded prior to joining. Therefore, we split the input on commas,
decode each result separately, and join the results with commas again.