Current section

Files

Jump to
grpcbox src grpcbox_utils.erl
Raw

src/grpcbox_utils.erl

-module(grpcbox_utils).
-export([headers_to_metadata/1,
maybe_decode_header/2,
decode_header/1,
encode_headers/1,
is_reserved_header/1,
status_to_string/1,
get_timeout_from_ctx/2]).
-include("grpcbox.hrl").
headers_to_metadata(H) ->
lists:foldl(fun({K, V}, Acc) ->
case is_reserved_header(K) of
true ->
Acc;
false ->
maps:put(K, maybe_decode_header(K, V), Acc)
end
end, #{}, H).
%% TODO: consolidate with grpc_lib. But have to update their header map to support
%% a list of values for a key.
maybe_decode_header(Key, Value) ->
case binary:longest_common_suffix([Key, <<"-bin">>]) == 4 of
true ->
decode_header(Value);
false ->
Value
end.
%% golang gRPC implementation does not add the padding that the Erlang
%% decoder needs...
decode_header(Base64) when byte_size(Base64) rem 4 == 3 ->
base64:decode(<<Base64/bytes, "=">>);
decode_header(Base64) when byte_size(Base64) rem 4 == 2 ->
base64:decode(<<Base64/bytes, "==">>);
decode_header(Base64) ->
base64:decode(Base64).
encode_headers([]) ->
[];
encode_headers([{Key, Value} | Rest]) ->
case binary:longest_common_suffix([Key, <<"-bin">>]) == 4 of
true ->
[{Key, base64:encode(Value)} | encode_headers(Rest)];
false ->
[{Key, Value} | encode_headers(Rest)]
end.
is_reserved_header(<<"content-type">>) -> true;
is_reserved_header(<<"grpc-message-type">>) -> true;
is_reserved_header(<<"grpc-encoding">>) -> true;
is_reserved_header(<<"grpc-message">>) -> true;
is_reserved_header(<<"grpc-status">>) -> true;
is_reserved_header(<<"grpc-timeout">>) -> true;
is_reserved_header(<<"grpc-status-details-bin">>) -> true;
is_reserved_header(<<"te">>) -> true;
is_reserved_header(_) -> false.
-spec status_to_string(binary()) -> binary().
status_to_string(?GRPC_STATUS_OK) ->
<<"OK">>;
status_to_string(?GRPC_STATUS_CANCELLED) ->
<<"CANCELLED">>;
status_to_string(?GRPC_STATUS_UNKNOWN) ->
<<"UNKNOWN">>;
status_to_string(?GRPC_STATUS_INVALID_ARGUMENT) ->
<<"INVALID_ARGUMENT">>;
status_to_string(?GRPC_STATUS_DEADLINE_EXCEEDED) ->
<<"DEADLINE_EXCEEDED">>;
status_to_string(?GRPC_STATUS_NOT_FOUND) ->
<<"NOT_FOUND">>;
status_to_string(?GRPC_STATUS_ALREADY_EXISTS) ->
<<"ALREADY_EXISTS">>;
status_to_string(?GRPC_STATUS_PERMISSION_DENIED) ->
<<"PERMISSION_DENIED">>;
status_to_string(?GRPC_STATUS_RESOURCE_EXHAUSTED) ->
<<"RESOURCE_EXHAUSTED">>;
status_to_string(?GRPC_STATUS_FAILED_PRECONDITION) ->
<<"FAILED_PRECONDITION">>;
status_to_string(?GRPC_STATUS_ABORTED) ->
<<"ABORTED">>;
status_to_string(?GRPC_STATUS_OUT_OF_RANGE) ->
<<"OUT_OF_RANGE">>;
status_to_string(?GRPC_STATUS_UNIMPLEMENTED) ->
<<"UNIMPLEMENTED">>;
status_to_string(?GRPC_STATUS_INTERNAL) ->
<<"INTERNAL">>;
status_to_string(?GRPC_STATUS_UNAVAILABLE) ->
<<"UNAVAILABLE">>;
status_to_string(?GRPC_STATUS_DATA_LOSS) ->
<<"DATA_LOSS">>;
status_to_string(?GRPC_STATUS_UNAUTHENTICATED) ->
<<"UNAUTHENTICATED">>;
status_to_string(Code) ->
<<"CODE_", Code/binary>>.
get_timeout_from_ctx(Ctx, DefaultTimeout) ->
case ctx:deadline(Ctx) of
undefined ->
DefaultTimeout;
infinity ->
infinity;
{Deadline, _} ->
Timeout = erlang:convert_time_unit(Deadline - erlang:monotonic_time(), native, millisecond),
case Timeout < 0 of
true -> 0;
false -> Timeout
end
end.