Packages
gpb
4.9.1
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_gen_json_decoders.erl
%%% Copyright (C) 2019 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA 02110-1301 USA
%%% @private
%%% @doc Generation of routines for converting JSON to internal format
-module(gpb_gen_json_decoders).
-export([format_exports/2]).
-export([format_top_function/3]).
-export([format_decoders/3]).
-include("../include/gpb.hrl").
-include("gpb_codegen.hrl").
-include("gpb_compile.hrl").
-include("gpb_decoders_lib.hrl").
-import(gpb_lib, [replace_term/2, replace_tree/2,
splice_trees/2, repeat_clauses/2]).
format_exports(Defs, Opts) ->
DoNif = proplists:get_bool(nif, Opts),
NoNif = not DoNif,
[?f("-export([from_json/2"),[", from_json/3" || NoNif], ?f("]).~n"),
[[[begin
NoWrapperFnName = gpb_lib:mk_fn(from_json_msg_, MsgName),
if DoNif ->
?f("-export([~p/1]).~n", [NoWrapperFnName]);
not DoNif ->
?f("-export([~p/1, ~p/2]).~n",
[NoWrapperFnName, NoWrapperFnName])
end
end
|| {{msg,MsgName}, _Fields} <- Defs],
"\n"]
|| gpb_lib:get_bypass_wrappers_by_opts(Opts)]].
format_top_function(Defs, AnRes, Opts) ->
case gpb_lib:contains_messages(Defs) of
true -> format_top_function_msgs(Defs, AnRes, Opts);
false -> format_top_function_no_msgs(Opts)
end.
format_decoders(Defs, AnRes, Opts) ->
[format_msg_decoders(Defs, AnRes, Opts)].
format_top_function_no_msgs(_Opts) ->
["-spec from_json(term(), atom()) -> no_return().\n",
gpb_codegen:format_fn(
from_json,
fun(_Json, _MsgName) ->
erlang:error({gpb_error, no_messages})
end),
"-spec from_json(term(), atom(), list()) -> no_return().\n",
gpb_codegen:format_fn(
from_json,
fun(_Json, _MsgName, _Opts) ->
erlang:error({gpb_error, no_messages})
end)].
format_top_function_msgs(Defs, AnRes, Opts) ->
Error = ("error({gpb_error," ++
"" "{from_json_failure," ++
"" " {Json, MsgName, {Class, Reason, StackTrace}}}})"),
FromJsonMsg1Catch_GetStackTraceAsPattern =
?f("from_json_1_catch(Json, MsgName, TrUserData) ->~n"
" try from_json_2_doit(MsgName, Json, TrUserData)~n"
" catch Class:Reason:StackTrace -> ~s~n"
" end.~n", [Error]),
FromJsonMsg1Catch_GetStackTraceAsCall =
?f("from_json_1_catch(Json, MsgName, TrUserData) ->~n"
" try from_json_2_doit(MsgName, Json, TrUserData)~n"
" catch Class:Reason ->~n"
" StackTrace = erlang:get_stacktrace(),~n"
" ~s~n"
" end.~n", [Error]),
DoNif = proplists:get_bool(nif, Opts),
[gpb_codegen:format_fn(
from_json,
fun(Json, MsgName) ->
call_self(Json, MsgName, [])
end),
gpb_codegen:format_fn(
from_json,
fun(Json, MsgName, Opts) ->
TrUserData = proplists:get_value(user_data, Opts),
from_json_1_catch(Json, MsgName, TrUserData)
end),
["-ifdef('OTP_RELEASE').\n", % This macro appeared in Erlang 21
FromJsonMsg1Catch_GetStackTraceAsPattern,
"-else.\n",
FromJsonMsg1Catch_GetStackTraceAsCall,
"-endif.\n\n"],
gpb_codegen:format_fn(
from_json_2_doit,
fun('MsgName', Json, TrUserData) ->
'Tr'('from-json-fn'(Json, 'TrUserData'), TrUserData)
end,
[repeat_clauses(
'MsgName',
[begin
ElemPath = [MsgName],
Transl = gpb_gen_translators:find_translation(
ElemPath, decode, AnRes),
[replace_term('MsgName', MsgName),
replace_term('Tr', Transl),
replace_term('from-json-fn',
gpb_lib:mk_fn(from_json_msg_, MsgName)),
splice_trees('TrUserData', if DoNif -> [];
true -> [?expr(TrUserData)]
end)]
end
|| {{msg, MsgName}, _Fields} <- Defs])])].
format_msg_decoders(Defs, AnRes, Opts) ->
[[[gpb_codegen:format_fn(
gpb_lib:mk_fn(from_json_msg_, MsgName),
fun(Bin) ->
%% The undefined is the default TrUserData
call_self(Bin, undefined)
end)
|| {{msg,MsgName},_Fields} <- Defs]
|| gpb_lib:get_bypass_wrappers_by_opts(Opts)],
[format_msg_decoder(Name, MsgDef, Defs, AnRes, Opts)
|| {_Type, Name, MsgDef} <- gpb_lib:msgs_or_groups(Defs)],
format_helpers(Defs, AnRes, Opts)].
format_msg_decoder(MsgName, MsgDef, Defs, AnRes, Opts) ->
%% Compared to decoding from protobuf wire-format (gpb_gen_decoders),
%% We do not need to do any merging here intertwined with decoding.
%% In Json, a key can occur only once.
%%
%% (In contrast, the protobuf wire-format is a stream of fields,
%% if a field occurs more than once, the value is to be merged:
%% - overwritten if it is an an optional or required scalar field,
%% - appended to the sequence if the field is a repeated,
%% - recursively merged if the field is another sub message.
%% )
%%
TrUserDataVar = ?expr(TrUserData),
InitExprs = gpb_decoders_lib:init_exprs(MsgName, MsgDef, Defs,
TrUserDataVar, AnRes, Opts),
IsProto3 = gpb:is_msg_proto3(MsgName, Defs),
FieldInfos = gpb_decoders_lib:calc_field_infos(MsgDef, IsProto3, Opts),
if MsgDef == [] ->
format_msg_decoder_no_fields(MsgName, Opts);
MsgDef /= [] ->
[format_msg_init_decoder(MsgName, InitExprs, FieldInfos,
TrUserDataVar, Opts),
format_msg_decoder_loop(MsgName, MsgDef,
TrUserDataVar, AnRes, Opts)]
end.
format_msg_decoder_no_fields(MsgName, Opts) ->
InitExprs1 = calc_init_exprs(MsgName, [], [], Opts),
gpb_codegen:format_fn(
gpb_lib:mk_fn(from_json_msg_, MsgName),
fun(_Json, _TrUserData) ->
'<init>'
end,
[replace_tree('<init>', InitExprs1)]).
format_msg_init_decoder(MsgName, InitExprs, FieldInfos, TrUserDataVar, Opts) ->
InitExprs1 = calc_init_exprs(MsgName, InitExprs, FieldInfos, Opts),
FromJAuxFn = gpb_lib:mk_fn(fj_msg_, MsgName),
gpb_codegen:format_fn(
gpb_lib:mk_fn(from_json_msg_, MsgName),
fun(Json, 'TrUserData') ->
'<fj_msg_MsgName>'(fj_next(fj_iter(Json)), '<init>', 'TrUserData')
end,
[replace_term('<fj_msg_MsgName>', FromJAuxFn),
replace_tree('<init>', InitExprs1),
replace_tree('TrUserData', TrUserDataVar)]).
calc_init_exprs(MsgName, InitExprs, FieldInfos, Opts) ->
MappingUnset = gpb_lib:get_mapping_and_unset_by_opts(Opts),
case MappingUnset of
records ->
gpb_lib:record_create(MsgName, InitExprs);
#maps{unset_optional=present_undefined} ->
gpb_lib:map_create(InitExprs, Opts);
#maps{unset_optional=omitted, oneof=tuples} ->
InitExprs1 = repeated_or_required_init_exprs(InitExprs, FieldInfos),
gpb_lib:map_create(InitExprs1, Opts);
#maps{unset_optional=omitted, oneof=flat} ->
InitExprs1 = repeated_or_required_init_exprs(InitExprs, FieldInfos),
gpb_lib:map_create(InitExprs1, Opts)
end.
repeated_or_required_init_exprs(InitExprs, FieldInfos) ->
[{FName, InitExpr}
|| {FName, InitExpr, Occ} <- kzip(InitExprs, FieldInfos),
Occ == required orelse Occ == repeated].
kzip(L1, L2) ->
lists:map(fun({{K, V1}, {K, V2}}) -> {K, V1, V2} end,
lists:zip(L1, L2)).
format_msg_decoder_loop(MsgName, MsgDef, TrUserDataVar, AnRes, Opts) ->
JValueExpr = ?expr(JValue),
EMsgVar = ?expr(EMsg),
Decodings = calc_decodings(MsgName, MsgDef,
JValueExpr, EMsgVar, TrUserDataVar,
AnRes, Opts),
gpb_codegen:format_fn(
gpb_lib:mk_fn(fj_msg_, MsgName),
fun({JKey, JValue, JRest}, EMsg, TrUserData) ->
EMsg2 = case JKey of
'<FieldNamePattern>' ->
'<decode-value-update-EMsg>';
_ ->
EMsg
end,
call_self(fj_next(JRest), EMsg2, TrUserData);
(none, EMsg, _TrUserData) ->
EMsg
end,
[repeat_clauses(
'<FieldNamePattern>',
[[replace_tree('<FieldNamePattern>', KeyPattern),
replace_tree('<decode-value-update-EMsg>', DecodeExpr)]
|| {KeyPattern, DecodeExpr} <- Decodings])]).
calc_decodings(MsgName, MsgDef, JValueExpr, EMsgVar, TrUserDataVar,
AnRes, Opts) ->
%% In Json, the oneof fields are like flat oneofs:
%%
%% message Msg {
%% oneof c {
%% uint32 alt_one = 1;
%% string alt_two = 2;
%% }
%% uint32 outside_oneof = 10;
%% }
%%
%% -> {"alt_one": 17}
%% or {"alt_two": "x"} in Json
%%
%% The Erlang-internal format (if maps) may be either
%% #{c => {alt_one, 18}} if option {maps_oneof,flat} is _not_ set, or
%% #{alt_one => 18} if option {maps_oneof,flat} is set.
%%
%% Additionally, in Json, "[p]arsers accept both the lowerCamelCase name
%% (or the one specified by the json_name option) and the original proto
%% field name."
%% (src: https://developers.google.com/protocol-buffers/docs/proto3#json)
%%
%% So build a mapping from Json field name (binary, atom or string)
%% to decoding-and-update expressions:
%%
KeyFormat = gpb_lib:json_key_format_by_opts(Opts),
lists:reverse(
gpb_lib:fold_msgdef_fields_o(
fun(#?gpb_field{name=FName}=Field, IsOneof, Acc)->
KeyPatterns = key_patterns(FName, KeyFormat),
DecExpr = mk_check_null_decode_value_update_field_expr(
MsgName, Field, IsOneof,
JValueExpr, EMsgVar, TrUserDataVar,
AnRes, Opts),
[{KP, DecExpr} || KP <- KeyPatterns] ++ Acc
end,
[],
MsgDef)).
key_patterns(FName, KeyFormat) ->
FNameS = atom_to_list(FName),
LowerCamelCase = gpb_lib:lower_camel_case(FNameS),
Strs = lists:usort([FNameS, LowerCamelCase]),
case KeyFormat of
atom ->
[erl_syntax:atom(JFName) || JFName <- Strs];
binary ->
%% Want <<"fname">> and not <<102,110,97,109,101>>
%% so make a text node
[erl_syntax:text(?ff("<<~p>>", [JFName])) || JFName <- Strs];
string ->
[erl_syntax:string(JFName) || JFName <- Strs]
end.
mk_check_null_decode_value_update_field_expr(
MsgName, #?gpb_field{type=Type, occurrence=Occurrence}=Field, IsOneof,
JValueExpr, EMsgVar, TrUserDataVar,
AnRes, Opts) ->
%% FIXME: handle repeated
%% FIXME: handle map<_,_>
%% FIXME: gpb_compile: remove transl gen workaround for no from_json calls
JNull = gpb_lib:json_null(Opts),
DecExpr =
case occurrence_or_mapfield(Occurrence, Type) of
optional -> type_decode_expr(Type, JValueExpr, TrUserDataVar);
required -> type_decode_expr(Type, JValueExpr, TrUserDataVar);
repeated -> repeated_field_decode_expr(MsgName, Field, JValueExpr,
TrUserDataVar, AnRes);
mapfield -> mapfield_decode_expr(MsgName, Field, JValueExpr,
TrUserDataVar, AnRes)
end,
?expr(if 'JValueExpr' =:= null ->
'EMsgVar';
true ->
'<update-field-expr>'
end,
[replace_tree('JValueExpr', JValueExpr),
replace_term(null, JNull),
replace_tree('EMsgVar', EMsgVar),
replace_tree('<update-field-expr>',
mk_decode_value_update_field_expr(
MsgName, Field, IsOneof,
EMsgVar, DecExpr, TrUserDataVar,
AnRes, Opts))]).
mk_decode_value_update_field_expr(MsgName, #?gpb_field{name=FName}, IsOneof,
EMsgVar, DecExpr, TrUserDataVar,
AnRes, Opts) ->
MappingUnset = gpb_lib:get_mapping_and_unset_by_opts(Opts),
case {MappingUnset, IsOneof} of
{records, false} ->
ElemPath = [MsgName, FName],
FValue = tr_wrap({ElemPath, decode, AnRes},
DecExpr,
TrUserDataVar),
?expr('<EMsg>'#'<MsgName>'{'<field-name>' = '<field-value>'},
[replace_tree('<EMsg>', EMsgVar),
replace_term('<MsgName>', MsgName),
replace_term('<field-name>', FName),
replace_tree('<field-value>', FValue)]);
{records, {true, CFName}} ->
%% Create CTr({tag, OTr(<decode-expr>, TrUserData)}, TrUserData)
CElemPath = [MsgName, CFName],
OElemPath = [MsgName, CFName, FName],
FValue = tr_wrap({CElemPath, decode, AnRes},
tag_wrap(FName,
tr_wrap({OElemPath, decode, AnRes},
DecExpr,
TrUserDataVar)),
TrUserDataVar),
?expr('<EMsg>'#'<MsgName>'{'<field-name>' = '<field-value>'},
[replace_tree('<EMsg>', EMsgVar),
replace_term('<MsgName>', MsgName),
replace_term('<field-name>', CFName),
replace_tree('<field-value>', FValue)]);
{#maps{oneof=tuples}, false} ->
ElemPath = [MsgName, FName],
FValue = tr_wrap({ElemPath, decode, AnRes},
DecExpr,
TrUserDataVar),
gpb_lib:map_set(EMsgVar, [{FName, FValue}], Opts);
{#maps{oneof=tuples}, {true, CFName}} ->
%% Create CTr({tag, OTr(<decode-expr>, TrUserData)}, TrUserData)
CElemPath = [MsgName, CFName],
OElemPath = [MsgName, CFName, FName],
FValue = tr_wrap({CElemPath, decode, AnRes},
tag_wrap(FName,
tr_wrap({OElemPath, decode, AnRes},
DecExpr,
TrUserDataVar)),
TrUserDataVar),
gpb_lib:map_set(EMsgVar, [{CFName, FValue}], Opts);
{#maps{oneof=flat}, false} ->
ElemPath = [MsgName, FName],
FValue = tr_wrap({ElemPath, decode, AnRes},
DecExpr,
TrUserDataVar),
gpb_lib:map_set(EMsgVar, [{FName, FValue}], Opts);
{#maps{oneof=flat}, {true, CFName}} ->
%% Generate maps:without(...)? To guarantee at most one oneof
%% field, also in case the json would be "malformed" containing
%% for instance both alt_one and alt_two?
CElemPath = [MsgName, CFName],
OElemPath = [MsgName, CFName, FName],
FValue = tr_wrap({CElemPath, decode, AnRes},
tr_wrap({OElemPath, decode, AnRes},
DecExpr,
TrUserDataVar),
TrUserDataVar),
gpb_lib:map_set(EMsgVar, [{FName, FValue}], Opts)
end.
repeated_field_decode_expr(MsgName, #?gpb_field{name=FName, type=Type},
JValueExpr, TrUserDataVar, AnRes) ->
[TrEmptySeq, TrAddElem, TrFinalize] =
[gpb_gen_translators:find_translation([MsgName, FName], Op, AnRes)
|| Op <- [decode_init_default,
decode_repeated_add_elem,
decode_repeated_finalize]],
ElemPath = [MsgName, FName, []],
JElemVar = gpb_lib:var("JElem@~s", [FName]),
ElemTrDecExpr = tr_wrap({ElemPath, decode, AnRes},
type_decode_expr(Type, JElemVar, TrUserDataVar),
TrUserDataVar),
?expr('lists:reverse'(
lists:foldl(
fun('JElem@FVar', Acc) ->
'[New|Acc]'('<decoded-elem>', Acc, 'TrUserData')
end,
'[]'([], 'TrUserData'),
fj_array('JValue')),
'TrUserData'),
[replace_tree('JValue', JValueExpr),
replace_tree('JElem@FVar', JElemVar),
replace_tree('<decoded-elem>', ElemTrDecExpr),
replace_term('lists:reverse', TrFinalize),
replace_term('[New|Acc]', TrAddElem),
%% Get it from EMsg instead to avoid translator called twice?
replace_term('[]', TrEmptySeq),
replace_tree('TrUserData', TrUserDataVar)]).
mapfield_decode_expr(MsgName, #?gpb_field{name=FName, type={map, KT, VT}=FType},
JValueExpr, TrUserDataVar, AnRes) ->
MapMsgName = gpb_lib:map_type_to_msg_name(KT, VT),
ElemPath = [MsgName, FName, []],
[TrEmptySeq, TrAddElem, TrFinalize] =
[gpb_gen_translators:find_translation([MsgName, FName], Op, AnRes)
|| Op <- [decode_init_default,
decode_repeated_add_elem,
decode_repeated_finalize]],
KElemDecExpr = tr_wrap(
{ElemPath, decode, AnRes},
map_key_type_decode_expr(KT, ?expr(Key), TrUserDataVar),
TrUserDataVar),
VElemDecExpr = tr_wrap(
{ElemPath, decode, AnRes},
type_decode_expr(VT, ?expr(Value), TrUserDataVar),
TrUserDataVar),
ElemDecExpr = tr_wrap(
{ElemPath, decode, AnRes},
type_decode_expr(FType, JValueExpr, TrUserDataVar),
TrUserDataVar),
?expr(fj_mapfield_fold(
'<map-name-as-msg>',
fun 'new-[]'/2,
fun 'add-elem-[New|Acc]'/3,
fun 'lists:reverse'/2,
fun(Key) -> '<ElemDecExpr(Key)>' end,
fun(Value) -> '<ElemDecExpr(Value)>' end,
'Tr(JValueExpr,TrUserData)',
'TrUserData'),
[replace_term('<map-name-as-msg>', MapMsgName),
%% to avoid translator called twice?
replace_term('new-[]', TrEmptySeq), % Get it from EMsg instead?
replace_term('add-elem-[New|Acc]', TrAddElem),
replace_term('lists:reverse', TrFinalize),
replace_tree('<ElemDecExpr(Key)>', KElemDecExpr),
replace_tree('<ElemDecExpr(Value)>', VElemDecExpr),
replace_tree('Tr(JValueExpr,TrUserData)', ElemDecExpr),
replace_tree('TrUserData', TrUserDataVar)]).
tr_wrap({ElemPath, Op, AnRes}, Expr, TrUserDataVar) ->
Tr = gpb_gen_translators:find_translation(ElemPath, Op, AnRes),
?expr('Tr'('<expr>', 'TrUserData'),
[replace_term('Tr', Tr),
replace_tree('<expr>', Expr),
replace_tree('TrUserData', TrUserDataVar)]).
tag_wrap(Tag, Expr) ->
?expr({'<tag>', '<expr>'},
[replace_term('<tag>', Tag),
replace_tree('<expr>', Expr)]).
map_key_type_decode_expr(Type, JValueExpr, TrUserDataVar) when Type /= bool ->
type_decode_expr(Type, JValueExpr, TrUserDataVar);
map_key_type_decode_expr(bool, JValueExpr, TrUserDataVar) ->
%% All types that can appear as map keys are acceptable
%% as string on JSON->internal. Except booleans, so
%% need to make an extra pass for them
JValueExpr2 = ?expr(case '<JValueExpr>' of
<<"true">> -> true;
<<"false">> -> false
end,
[replace_tree('<JValueExpr>', JValueExpr)]),
type_decode_expr(bool, JValueExpr2, TrUserDataVar).
type_decode_expr(Type, JValueExpr, TrUserDataVar) ->
case Type of
Int32 when Int32 == sint32;
Int32 == int32;
Int32 == uint32 ->
?expr(fj_int('Var'),
[replace_tree('Var', JValueExpr)]);
Int64 when Int64 == sint64;
Int64 == int64;
Int64 == uint64 ->
?expr(fj_int('Var'),
[replace_tree('Var', JValueExpr)]);
bool ->
?expr(fj_bool('Var'),
[replace_tree('Var', JValueExpr)]);
{enum,EnumName} ->
DecodeEnumFn = gpb_lib:mk_fn(fj_enum_, EnumName),
?expr('fj_enum_<EName>'('Var'),
[replace_tree('Var', JValueExpr),
replace_term('fj_enum_<EName>', DecodeEnumFn)]);
Int32 when Int32 == fixed32;
Int32 == sfixed32 ->
?expr(fj_int('Var'),
[replace_tree('Var', JValueExpr)]);
Int64 when Int64 == fixed64;
Int64 == sfixed64 ->
?expr(fj_int('Var'),
[replace_tree('Var', JValueExpr)]);
Float when Float == float;
Float == double ->
?expr(fj_float('Var'),
[replace_tree('Var', JValueExpr)]);
string ->
?expr(fj_string('Var'),
[replace_tree('Var', JValueExpr)]);
bytes ->
?expr(fj_bytes('Var'),
[replace_tree('Var', JValueExpr)]);
{msg,MsgName} ->
FnName = gpb_lib:mk_fn(from_json_msg_, MsgName),
?expr('from_json_msg_<MsgName>'('Var', 'TrUserData'),
[replace_term('from_json_msg_<MsgName>', FnName),
replace_tree('Var', JValueExpr),
replace_tree('TrUserData', TrUserDataVar)]);
{map,_KT,_VT} ->
JValueExpr; % handled on a higher level
{group,GName} ->
FnName = gpb_lib:mk_fn(from_json_msg_, GName),
?expr('from_json_msg_<GName>'('Var', 'TrUserData'),
[replace_term('from_json_msg_<GName>', FnName),
replace_tree('Var', JValueExpr),
replace_tree('TrUserData', TrUserDataVar)])
end.
format_helpers(Defs, AnRes, Opts) ->
[format_json_msg_iterator_helpers(Defs, Opts),
format_json_array_helpers(Defs, AnRes, Opts),
format_mapfield_helper(AnRes, Opts),
format_json_type_helpers(Defs, AnRes, Opts)].
format_json_msg_iterator_helpers(Defs, Opts) ->
HaveNonemptyMsgs = have_nonempty_msg(Defs),
HaveMapIterators = gpb_lib:target_has_map_iterators(Opts),
[[case gpb_lib:json_object_format_by_opts(Opts) of
eep18 ->
[gpb_codegen:format_fn(
fj_iter,
fun([{}]) -> [];
(Proplist) -> Proplist
end),
gpb_codegen:format_fn(
fj_next,
fun([{Key,Value} | Rest]) -> {Key, Value, Rest};
([]) -> none
end)];
{proplist} ->
[gpb_codegen:format_fn(
fj_iter,
fun({Proplist}) -> Proplist
end),
gpb_codegen:format_fn(
fj_next,
fun([{Key,Value} | Rest]) -> {Key, Value, Rest};
([]) -> none
end)];
{Tag, proplist} ->
[gpb_codegen:format_fn(
fj_iter,
fun({struct, Proplist}) -> Proplist
end,
[replace_term(struct, Tag)]),
gpb_codegen:format_fn(
fj_next,
fun([{Key,Value} | Rest]) -> {Key, Value, Rest};
([]) -> none
end)];
map when HaveMapIterators ->
[gpb_codegen:format_fn(
fj_iter,
fun(Map) -> maps:iterator(Map)
end),
gpb_codegen:format_fn(
fj_next,
fun(Iter) -> maps:next(Iter)
end)];
map when not HaveMapIterators ->
[gpb_codegen:format_fn(
fj_iter,
fun(Map) -> maps:to_list(Map)
end),
gpb_codegen:format_fn(
fj_next,
fun([{Key,Value} | Rest]) -> {Key, Value, Rest};
([]) -> none
end)]
end] || HaveNonemptyMsgs].
format_json_array_helpers(Defs, #anres{map_types=MapTypes}, Opts) ->
HaveMapfields = sets:size(MapTypes) > 0,
HaveRepeated = have_repeated_fields(Defs) orelse HaveMapfields,
[[case gpb_lib:json_array_format_by_opts(Opts) of
list ->
[gpb_lib:nowarn_unused_function(fj_array, 1),
gpb_codegen:format_fn(
fj_array,
fun(L) -> L end)];
{Tag, list} ->
[gpb_lib:nowarn_unused_function(fj_array, 1),
gpb_codegen:format_fn(
fj_array,
fun({array, L}) -> L end,
[replace_term(array, Tag)])]
end] || HaveRepeated].
format_mapfield_helper(#anres{map_types=MapTypes}, Opts) ->
HaveMapfields = sets:size(MapTypes) > 0,
[[gpb_codegen:format_fn(
fj_mapfield_fold,
fun(MapMsgName, New, AddElem, Finalize, DecodeKey, DecodeValue,
JMapfield, TrUserData) ->
Finalize(
fj_mapfield_fold_aux(fj_iter(JMapfield),
MapMsgName, DecodeKey, DecodeValue,
AddElem,
New([], TrUserData),
TrUserData),
TrUserData)
end),
case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of
'2tuples' ->
gpb_codegen:format_fn(
fj_mapfield_fold_aux,
fun(JIter, MapMsgName, DecodeKey, DecodeValue,
AddElem, Acc, TrUserData) ->
case fj_next(JIter) of
{JKey, JValue, JRest} ->
EKey = DecodeKey(JKey),
EValue = DecodeValue(JValue),
TmpMsg = {MapMsgName, EKey, EValue},
Acc1 = AddElem(TmpMsg, Acc, TrUserData),
call_self(JRest,
MapMsgName, DecodeKey, DecodeValue,
AddElem, Acc1, TrUserData);
none ->
Acc
end
end);
maps ->
{EKey, EValue} = {?expr(EKey), ?expr(EValue)},
MkMapExpr = gpb_lib:map_create([{key, EKey}, {value, EValue}],
Opts),
gpb_codegen:format_fn(
fj_mapfield_fold_aux,
fun(JIter, MapMsgName, DecodeKey, DecodeValue,
AddElem, Acc, TrUserData) ->
case fj_next(JIter) of
{JKey, JValue, JRest} ->
EKey = DecodeKey(JKey),
EValue = DecodeValue(JValue),
TmpMsg = '#{key => EKey, value => EValue}',
Acc1 = AddElem(TmpMsg, Acc, TrUserData),
call_self(JRest,
MapMsgName, DecodeKey, DecodeValue,
AddElem, Acc1, TrUserData);
none ->
Acc
end
end,
[replace_tree('#{key => EKey, value => EValue}', MkMapExpr)])
end] || HaveMapfields].
format_json_type_helpers(Defs, #anres{used_types=UsedTypes}, Opts) ->
StrBin = gpb_lib:get_strings_as_binaries_by_opts(Opts),
NeedIntType = lists:any(fun(T) -> gpb_lib:smember(T, UsedTypes) end,
[sint32, int32, uint32,
sint64, int64, uint64,
fixed32, sfixed32,
fixed64, sfixed64]),
NeedBoolType = gpb_lib:smember(bool, UsedTypes),
NeedEnumType = lists:any(fun({enum,_}) -> true;
(_) -> false
end,
sets:to_list(UsedTypes)),
NeedFloatType = lists:any(fun(T) -> gpb_lib:smember(T, UsedTypes) end,
[float, double]),
NeedStringType = gpb_lib:smember(string, UsedTypes),
NeedBytesType = gpb_lib:smember(bytes, UsedTypes),
[[gpb_codegen:format_fn(
fj_int,
%% Leading zeros are not allowed according to RFC7159,
%% so no octal representation decoding or anything such is needed.
fun(N) when is_integer(N) ->
N;
(S) when is_binary(S) ->
list_to_integer(binary_to_list(S));
(S) when is_list(S) ->
list_to_integer(S)
end) || NeedIntType],
[[%% The protobuf also accepts both boolean values as well as
%% string representation of the same. It seems to also accepts
%% string representations of 0 and 1, but oddly enough
%% not the integers 0 and 1. (protobuf 3.8.0-rc1)
gpb_codegen:format_fn(
fj_bool,
fun(B) when is_boolean(B) -> B;
(B) when is_binary(B) ->
case fj_bool_bin_casecanon(B, <<>>) of
(<<"true">>) -> true;
(<<"false">>) -> false;
(<<"1">>) -> true;
(<<"0">>) -> false
end;
(S) when is_list(S) ->
call_self(list_to_binary(S))
end),
gpb_codegen:format_fn(
fj_bool_bin_casecanon, % to lowercase
fun(<<C, Rest/binary>>, Acc) when $A =< C, C =< $Z ->
call_self(Rest, <<Acc/binary, (C + 32)>>); % $a - $A == 32
(<<C, Rest/binary>>, Acc) ->
call_self(Rest, <<Acc/binary, C>>);
(<<>>, Acc) ->
Acc
end)]
|| NeedBoolType],
[begin
{Syms, Ints} = lists:unzip(Enums),
IntStrs = [integer_to_list(I) || I <- Ints],
CanonicalSymStrs = [enum_canonical_str(Sym, Opts) || Sym <- Syms],
StrSyms = (lists:zip(CanonicalSymStrs, Syms)
++ lists:zip(IntStrs, Syms)),
gpb_codegen:format_fn(
gpb_lib:mk_fn(fj_enum_, EnumName),
fun(S) when is_binary(S) ->
case fj_casecanon_enum(S, <<>>) of
'<<"Str">>' -> '<EnumSym>';
_ -> 'FirstSym'
end;
(N) when is_integer(N) ->
'd_enum_<EName>'(N);
(S) when is_list(S) ->
call_self(list_to_binary(S))
end,
[replace_term('d_enum_<EName>', gpb_lib:mk_fn(d_enum_, EnumName)),
replace_term('FirstSym', Sym1),
repeat_clauses(
'<<"Str">>',
[[replace_tree('<<"Str">>', bstr(Str)),
replace_term('<EnumSym>', Sym)]
|| {Str, Sym} <- StrSyms])])
end
|| {{enum,EnumName}, [{Sym1,_} | _]=Enums} <- Defs,
NeedEnumType],
[%% Extra enum helper(s)
case proplists:get_bool(json_case_insensitive_enum_parsing, Opts) of
true ->
[gpb_codegen:format_fn(
fj_casecanon_enum,
fun(<<C, Tl/binary>>, Acc) ->
call_self(Tl, <<Acc/binary, (fj_casecanon_char(C))>>);
(<<>>, Acc) ->
Acc
end),
gpb_codegen:format_fn(
fj_casecanon_char, % to uppercase
fun(C) when $a =< C, C =< $z -> C - 32; % $a - $A == 32
($-) -> $_;
(C) -> C
end)];
false ->
[gpb_codegen:format_fn(
fj_casecanon_enum,
fun(B, _Acc) ->
B
end)]
end
|| NeedEnumType],
[[%% float with helper
gpb_codegen:format_fn(
fj_float,
fun(N) when is_integer(N) -> float(N);
(N) when is_float(N) -> N;
(S) when is_binary(S) -> call_self(binary_to_list(S));
("NaN") -> nan;
("Infinity") -> infinity;
("-Infinity") -> '-infinity';
(S) when is_list(S) ->
case fj_d_num2e(S, integer, "") of
{integer, S2} -> float(list_to_integer(S2));
{float, S2} -> list_to_float(S2)
end
end),
gpb_codegen:format_fn(
fj_d_num2e,
fun("-"++Rest, St, Acc) ->
call_self(Rest, St, "-" ++ Acc);
("+"++Rest, St, ""=Acc) ->
%% Ignore leading plus
call_self(Rest, St, Acc);
([D | Rest], St, Acc) when $0 =< D, D =< $9 ->
call_self(Rest, St, [D | Acc]);
("."++_ = S, _, Acc) when Acc =:= ""; Acc =:= "-" ->
%% Initial leading decimal point: prepend a leading zero
%% and continue processing
call_self(S, float, "0" ++ Acc);
("."++[D|_] = S, _, Acc) when $0 =< D, D =< $9 ->
%% A decimal point: the rest (if wellformed as per rfc 7159)
%% will be parseable as an erlang float
{float, lists:reverse(Acc, S)};
("."++Rest, _, Acc) ->
%% A decimal point followed by non-digit (exponent or
%% or end of string): Turn "." into ".0" to make
%% it parseable as an Erlang float
{float, lists:reverse(Acc, ".0" ++ Rest)};
([E | _]=Rest, integer, Acc) when E == $e; E == $E ->
%% Exponent: not preceded by a decimal point:
%% add ".0" before the exponent, and it will (if wellformed)
%% be parseable as an erlang float
{float, lists:reverse(Acc, ".0"++Rest)};
("", St, Acc) ->
{St, lists:reverse(Acc)}
end)] || NeedFloatType],
[[%% String
case StrBin of
true ->
gpb_codegen:format_fn(
fj_string,
fun(S) when is_binary(S); is_list(S) ->
unicode:characters_to_binary(S)
end);
false ->
gpb_codegen:format_fn(
fj_string,
fun(S) when is_binary(S); is_list(S) ->
unicode:characters_to_list(S)
end)
end || NeedStringType]],
[gpb_codegen:format_fn(
fj_bytes,
fun(S) when is_binary(S); is_list(S) ->
%% Convert any url-safe encoding to normal base64 encoding
B64 = <<<<if C =:= $- -> $+;
C =:= $_ -> $/;
true -> C
end>>
|| <<C>> <= iolist_to_binary(S)>>,
base64:decode(B64)
end) || NeedBytesType]].
occurrence_or_mapfield(repeated, {map, _, _}) -> mapfield;
occurrence_or_mapfield(Occurrence, _) -> Occurrence.
have_nonempty_msg([{{msg,_MsgName}, Fields} | Rest]) ->
if Fields /= [] -> true;
Fields == [] -> have_nonempty_msg(Rest)
end;
have_nonempty_msg([_ | Rest]) ->
have_nonempty_msg(Rest);
have_nonempty_msg([]) ->
false.
have_repeated_fields([{{msg,_Msg}, Fields} | Rest]) ->
case have_repeated_aux(Fields) of
true -> true;
false -> have_repeated_fields(Rest)
end;
have_repeated_fields([_ | Rest]) ->
have_repeated_fields(Rest);
have_repeated_fields([]) ->
false.
have_repeated_aux([#?gpb_field{occurrence = repeated} | _]) ->
true;
have_repeated_aux([_ | Rest]) ->
have_repeated_aux(Rest);
have_repeated_aux([]) ->
false.
bstr(S) when is_list(S) ->
%% Want <<"str">> and not <<102,110,97,109,101>>
%% so make a text node
erl_syntax:text(?ff("<<~p>>", [S])).
enum_canonical_str(A, _Opts) when is_atom(A) ->
gpb_lib:uppercase(atom_to_list(A)).