-module(livery_grpc_compression). -moduledoc """ gRPC per-message compression. Supports `identity` (no compression) and `gzip`. The active algorithm is negotiated through the `grpc-encoding` (what this message used) and `grpc-accept-encoding` (what the peer can decode) headers. A frame's compressed flag (see `livery_grpc_frame`) says whether its payload went through `compress/2`; `identity` always leaves the flag clear. """. -export([compress/2, decompress/3]). -export([is_supported/1, from_header/1, request_algorithm/1, accept_header/0]). -export_type([algorithm/0]). -type algorithm() :: identity | gzip. -doc """ Compress a payload. Returns `{Compressed, Bytes}`: `Compressed` is the boolean to put in the frame flag. `identity` never marks the frame. """. -spec compress(algorithm(), binary()) -> {boolean(), binary()}. compress(identity, Bin) -> {false, Bin}; compress(gzip, Bin) -> {true, zlib:gzip(Bin)}. -doc """ Decompress a payload given the frame's compressed flag and the message encoding. A clear flag means the payload is identity-coded regardless of the negotiated algorithm (per the gRPC spec, the flag wins per message). A set flag with an `identity` encoding is malformed and raises `{grpc_compression, flag_set_for_identity}`. """. -spec decompress(boolean(), algorithm(), binary()) -> binary(). decompress(false, _Algorithm, Bin) -> Bin; decompress(true, gzip, Bin) -> zlib:gunzip(Bin); decompress(true, identity, _Bin) -> error({grpc_compression, flag_set_for_identity}). -doc "Whether an algorithm atom is supported.". -spec is_supported(algorithm() | term()) -> boolean(). is_supported(identity) -> true; is_supported(gzip) -> true; is_supported(_) -> false. -doc """ Parse a `grpc-encoding` header value into an algorithm. An absent header (`undefined`) or an unknown value falls back to `identity`. """. -spec from_header(binary() | undefined) -> algorithm(). from_header(undefined) -> identity; from_header(<<"identity">>) -> identity; from_header(<<"gzip">>) -> gzip; from_header(_Other) -> identity. -doc """ Resolve a request's `grpc-encoding` strictly: `{ok, Algorithm}` for one we support, or `{error, Value}` for an unsupported one. Unlike `from_header/1` (which falls back to `identity`), this lets the server return `unimplemented` for a message compressed with an algorithm it cannot decode, as the gRPC spec requires. """. -spec request_algorithm(binary() | undefined) -> {ok, algorithm()} | {error, binary()}. request_algorithm(undefined) -> {ok, identity}; request_algorithm(<<"identity">>) -> {ok, identity}; request_algorithm(<<"gzip">>) -> {ok, gzip}; request_algorithm(Other) when is_binary(Other) -> {error, Other}. -doc "The `grpc-accept-encoding` value advertising what we can decode.". -spec accept_header() -> binary(). accept_header() -> <<"identity,gzip">>.