Current section

Files

Jump to
kafka_protocol src kpro_rsp_lib.erl
Raw

src/kpro_rsp_lib.erl

%%% Copyright (c) 2018, Klarna AB
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
-module(kpro_rsp_lib).
-export([ decode/4
, dec_struct/4
]).
-include("kpro_private.hrl").
-define(IS_STRUCT_SCHEMA(Schema), is_list(Schema)).
%% @doc Decode message body binary (without the leading 4-byte correlation ID).
-spec decode(kpro:api(), kpro:vsn(), binary(),
false | reference()) -> kpro:rsp().
decode(API, Vsn, Body0, Ref) ->
Body =
case Vsn >= kpro_schema:min_flexible_vsn(API) of
true ->
%% Discard tagged feilds for response header for now
%% As it currently not used and there is no clear plan
%% that it will be used in the near future.
{_TaggedFields, B} = dec(tagged_fields, Body0),
B;
false ->
Body0
end,
{Message, <<>>} =
try
decode_struct(API, Vsn, Body)
catch error : E ?BIND_STACKTRACE(Stack) ->
Context = [ {api, API}
, {vsn, Vsn}
, {body, Body}
],
?GET_STACKTRACE(Stack),
erlang:raise(error, {E, Context}, Stack)
end,
#kpro_rsp{ ref = Ref
, api = API
, vsn = Vsn
, msg = Message
}.
%% @doc Decode struct.
dec_struct([], Fields, _Stack, Bin) ->
{Fields, Bin};
dec_struct([{Name, FieldSc} | Schema], Fields, Stack, Bin) ->
NewStack = [Name | Stack],
{Value0, Rest} = dec_struct_field(FieldSc, NewStack, Bin),
Value = translate(NewStack, Value0),
dec_struct(Schema, Fields#{Name => Value}, Stack, Rest).
%%%_* Internal functions =======================================================
%% Decode prmitives.
dec(Type, Bin) -> kpro_lib:decode(Type, Bin).
decode_struct(API, Vsn, Bin) ->
Schema = kpro_lib:get_rsp_schema(API, Vsn),
dec_struct(Schema, #{}, _Stack = [{API, Vsn}], Bin).
%% A struct field should have one of below types:
%% 1. An array of any
%% 2. Another struct
%% 3. A user define decoder
%% 4. A primitive
dec_struct_field({A, Schema}, Stack, Bin0) when A =:= array orelse
A =:= compact_array ->
{Count, Bin} =
case A of
array ->
dec(int32, Bin0);
compact_array ->
{C, B} = dec(unsigned_varint, Bin0),
{C - 1, B}
end,
case Count =:= -1 of
true -> {?null, Bin};
false -> dec_array_elements(Count, Schema, Stack, Bin, [])
end;
dec_struct_field(Schema, Stack, Bin) when ?IS_STRUCT_SCHEMA(Schema) ->
dec_struct(Schema, #{}, Stack, Bin);
dec_struct_field(F, _Stack, Bin) when is_function(F) ->
%% Caller provided decoder
F(Bin);
dec_struct_field(Primitive, DecodeStack, Bin) when is_atom(Primitive) ->
try
dec(Primitive, Bin)
catch
error : Reason ?BIND_STACKTRACE(CallStack) ->
?GET_STACKTRACE(CallStack),
erlang:raise(error, {Primitive, Bin, DecodeStack, Reason}, CallStack)
end.
dec_array_elements(0, _Schema, _Stack, Bin, Acc) ->
{lists:reverse(Acc), Bin};
dec_array_elements(N, Schema, Stack, Bin, Acc) ->
{Element, Rest} = dec_struct_field(Schema, Stack, Bin),
dec_array_elements(N-1, Schema, Stack, Rest, [Element | Acc]).
%% Translate error codes; Dig up embedded bytes. etc.
translate([api_key | _], ApiKey) ->
try
kpro_schema:api_key(ApiKey)
catch
error : {not_supported, _}->
%% Not supported, perhaps a broker-only API, discard
ApiKey
end;
translate([error_code | _], ErrorCode) ->
kpro_schema:ec(ErrorCode);
translate([metadata, members | _] = Stack, Bin) ->
Schema = kpro_lib:get_prelude_schema(cg_member_metadata, 0),
case Bin =:= <<>> of
true -> ?kpro_cg_no_member_metadata;
false -> dec_struct_clean(Schema, [{cg_member_metadata, 0} | Stack], Bin)
end;
translate([assignment | _], <<>>) ->
?kpro_cg_no_assignment; %% no assignment for this member
translate([assignment | _] = Stack, Bin) ->
Schema = kpro_lib:get_prelude_schema(cg_memeber_assignment, 0),
dec_struct_clean(Schema, [{cg_memeber_assignment, 0} | Stack], Bin);
translate([isolation_level | _], Integer) ->
?ISOLATION_LEVEL_ATOM(Integer);
translate(_Stack, Value) ->
Value.
%% Decode struct, assume no tail bytes.
dec_struct_clean(Schema, Stack, Bin) ->
{Fields, <<>>} = dec_struct(Schema, #{}, Stack, Bin),
Fields.
%%%_* Emacs ====================================================================
%%% Local Variables:
%%% allout-layout: t
%%% erlang-indent-level: 2
%%% End: