Current section
Files
Jump to
Current section
Files
lib/spear/grpc.ex
defmodule Spear.Grpc do
@moduledoc false
# gRPC utilities for making requests to the EventStoreDB
#
# You could say this little mint-backed client is the "thrust"
# of the library (hehe).
def user_agent do
mint_version =
case :application.get_key(:mint, :vsn) do
{:ok, version} -> version
_ -> '0.0.0'
end
spear_version =
case :application.get_key(:spear, :vsn) do
{:ok, version} -> version
_ -> '0.0.0'
end
"grpc-elixir-spear/#{spear_version} (mint #{mint_version}; Elixir #{System.version()}; OTP #{System.otp_release()})"
end
# happy path of parsing DATA frame(s)
#
# from the specification (https://github.com/grpc/grpc/blob/fd27fb09b028e5823f3f246e90bad2d02dfd90d3/doc/PROTOCOL-HTTP2.md)
# format: ABNF
#
# begin quote
#
# The repeated sequence of Length-Prefixed-Message items is delivered in DATA frames
#
# * Length-Prefixed-Message → Compressed-Flag Message-Length Message
# * Compressed-Flag → 0 / 1 # encoded as 1 byte unsigned integer
# * Message-Length → {length of Message} # encoded as 4 byte unsigned integer (big endian)
# * Message → *{binary octet}
#
# end quote
#
# notes:
# - "1 byte unsigned integer" is 8 bits, hence `unsigned-integer-8`
# - `unit(4)` captures 4 bytes for the message_length
# - endianness does not matter when the binary in question is 1 byte
# hence big/little is not specified for `compressed_flag` (however the
# default according to the Elixir documentation is big endian).
# - it _does_ matter for message_length however which is 4 bytes
# - this is why the specification says "big endian" for the message_length
# - the signature of this function makes it very easy to use with
# Stream.unfold/2
@spec decode_next_message(binary(), {module(), atom()}) :: nil | {tuple(), binary()}
def decode_next_message(
<<0::unsigned-integer-8, message_length::unsigned-big-integer-8-unit(4),
encoded_message::binary-size(message_length), rest::binary>>,
{module, type}
) do
# YARD decompression
{module.decode_msg(encoded_message, type), rest}
end
def decode_next_message(_empty_or_malformed, {_module, _type}), do: nil
end