Permalink
Browse files

Fix uniform marshalling

 + Set all keys to be binary to avoid atom buffer overflow
 + Copy marshalling XML test to JSON
 + Make JSON lists behave more like DOM NodeList to get an equal
   representation for all marshallers
  • Loading branch information...
1 parent 2a3df54 commit cb8719003f4dbc96c231069a1c0c4b6bf7277a40 @lafka committed Sep 5, 2012
Showing with 87 additions and 17 deletions.
  1. +3 −3 src/tavern_marshal.erl
  2. +61 −7 src/tavern_marshal_json.erl
  3. +23 −7 src/tavern_marshal_xml.erl
View
@@ -26,7 +26,7 @@ map_mime(<<"text/html">>) -> tavern_marshal_html;
map_mime(A) when is_binary(A) -> tavern_marshal_plain.
-ifdef(TEST).
- -define(TREE, [{root, [{child, <<"value">>}]}]).
+ -define(TREE, [{<<"root">>, [{<<"child">>, <<"value">>}]}]).
map_empty_mime_test() ->
tavern_marshal_plain = map_mime(<<>>).
@@ -46,7 +46,7 @@ map_mime(A) when is_binary(A) -> tavern_marshal_plain.
?assertEqual(?TREE, DecJSON).
encode_invalid_data_test() ->
- {error, _} = decode({<<"application">>, <<"json">>}, <<"invalid[data">>),
+ {error, _} = decode({<<"application">>, <<"json">>}, <<"invalid[data">>),
{error, _} = decode({<<"application">>, <<"xml">>}, <<"invalid#>data">>).
-
+
-endif.
@@ -2,11 +2,14 @@
-export([decode/1, encode/1]).
+-include_lib("eunit/include/eunit.hrl").
+
-spec decode(binary()) -> {ok, Data :: tavern_http:tree()} | {error, Error :: atom()}.
decode(Payload) ->
case (catch mochijson2:decode(Payload)) of
- {struct, _} = Res -> {ok, decode_mochistruct(Res)};
- _ -> {error, 'invalid json payload'}
+ {struct, _} = Res -> {ok, decode_mochistruct(Res)};
+ Struct when is_list(Struct) -> {ok, decode_mochistruct(Struct)};
+ _ -> {error, {'invalid json payload'}}
end.
-spec encode(tavern_http:tree()) -> {ok, Data :: iolist()} | {error, Error :: atom()}.
@@ -16,8 +19,59 @@ encode(Payload) ->
Data -> {ok, Data}
end.
--spec decode_mochistruct({struct, [any()]}) -> [any()].
-decode_mochistruct({struct, Struct}) ->
- [{binary_to_existing_atom(A, utf8), decode_mochistruct(B)} || {A, B} <- Struct];
-decode_mochistruct(Struct) ->
- Struct.
+%% Optimistic, we don't have a "list" in the JSON sense so we can assume
+%% a list containing a single object is actually a multi level object.
+%% This is to allow XML like <root><child>val</child></root> to be the same
+%% as both {root : {child : "val"}} AND {root : [{child : "val"}]}.
+-spec decode_mochistruct({struct, [any()] | [any()]}) -> [any()].
+decode_mochistruct({struct, Struct}) -> decode_mochistruct(Struct);
+decode_mochistruct([{Key, Struct}]) -> decode_mochistruct({Key, Struct});
+decode_mochistruct({Key, Struct}) -> {Key, decode_mochistruct(Struct)};
+decode_mochistruct(List) when is_list(List) -> [decode_mochistruct(A) || A <- List];
+decode_mochistruct(Val) -> Val.
+
+
+-ifdef(TEST).
+ encode_single_level_test() ->
+ {ok, _} = encode([{level1, [{level2, "value"}]}]),
+ {ok, _} = encode([{level1, [{level2, "value"}, {level2, "value2"}]}]).
+
+ encode_datatypes_test() ->
+ {ok, _} = encode([{level1, [{level2, atomvalue}]}]),
+ {ok, _} = encode([{level1, [{level2, <<"binaryval">>}]}]),
+ {ok, _} = encode([{level1, [{level2, 1.23}]}]),
+ {ok, _} = encode([{level1, [{level2, 1000000}]}]),
+ {ok, _} = encode([{level1, [{level2, "stringvalue"}]}]).
+
+ encode_multilevel_test() ->
+ {ok, _} = encode([{level1, [{level2, [{level3, [{level4, <<"value">>}]}]}]}]).
+
+ encode_common_elem_test() ->
+ {ok, _} = encode([{level1, [
+ {level2a, <<"value A">>}
+ , {level2b, <<"value B">>}
+ , {level2c, <<"value C">>}
+ , {level2d, <<"value D">>}
+ ]}]).
+
+ decode_test() ->
+ {ok, {<<"t0">>, <<"v0">>}} = decode(
+ <<"[{\"t0\" : \"v0\"}]">>),
+ {ok, {<<"t1">>,{<<"k2">>, <<"v2">>}}} = decode(
+ <<"{\"t1\" : {\"k2\" : \"v2\"}}">>),
+ {ok, {<<"t2">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}} = decode(
+ <<"{\"t2\":[{\"k2\":\"v2\"}, {\"k3\" : \"v3\"}]}">>),
+ {ok, {<<"t3">>,{<<"k2">>, <<"v2">>}}} = decode(
+ <<"{\"t3\":[{\"k2\":\"v2\"}]}">>),
+ {ok, {<<"t4">>,[{<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]}} = decode(
+ <<"[{\"t4\":[{\"k2\":\"v2\"},{\"k3\":\"v3\"}]}]">>),
+ {ok, [{<<"t5">>,{<<"k1">>, <<"v1">>}}, {<<"t5a">>, <<"v2">>}]} = decode(
+ <<"[{\"t5\":{\"k1\":\"v1\"}}, {\"t5a\":\"v2\"}]">>).
+
+% encode_decode_test() ->
+% {ok, JSON} = encode([{level1, [{level2, [{level3, [{level4, <<"value">>}]}]}]}]),
+% {ok, _} = decode(JSON).
+%
+% encode_binary_key_test() ->
+% {ok, _} = encode([{<<"level1">>, [{<<"level2">>, "value"}]}]).
+-endif.
View
@@ -10,14 +10,14 @@ decode(Payload) when is_binary(Payload) ->
decode(binary_to_list(Payload));
decode(Payload) ->
case (catch xmerl_scan:string(Payload)) of
- {#xmlElement{name = K, content = V}, _} -> {ok, [{K, decode_xmerl(V)}]};
+ {#xmlElement{name = K, content = V}, _} -> {ok, [{atom_to_binary(K, utf8), decode_xmerl(V)}]};
_ -> {error, 'invalid xml payload'}
end.
-spec encode(tavern_http:tree()) -> {ok, Data :: binary()} | {error, Error :: atom()}.
encode(Payload) ->
case (catch xmerl:export_simple(encode_list(Payload), xmerl_xml)) of
- {'EXIT', _} -> {error, 'xml serialization failed'};
+ {'EXIT', A} -> {error, {'xml serialization failed', A}};
Data -> {ok, Data}
end.
@@ -30,19 +30,29 @@ encode_list([], Acc) ->
lists:reverse(Acc);
encode_list([{Key, Payload} | Tail], Acc) ->
- encode_list(Tail, [{Key, [{A, encode_value(B)} || {A, B} <- Payload]} | Acc]).
+ encode_list(Tail, [{encode_key(Key), [{encode_key(A), encode_value(B)} || {A, B} <- Payload]} | Acc]);
+encode_list([Value | Tail], Acc) ->
+ encode_list(Tail, [encode_value(Value) | Acc]).
-spec encode_value(binary() | atom() | integer() | any()) -> list().
-encode_value(V) when is_binary(V) -> [binary_to_list(V)];
-encode_value(V) when is_atom(V) -> [atom_to_list(V)];
-encode_value(V) when is_float(V); is_integer(V) -> [mochinum:digits(V)];
+encode_value(V) when is_binary(V) -> [binary_to_list(V)];
+encode_value([V]) when is_binary(V) -> [binary_to_list(V)];
+encode_value(V) when is_atom(V) -> [atom_to_list(V)];
+encode_value(V) when is_float(V); is_integer(V) -> [mochinum:digits(V)];
encode_value([{_, _} | _ ] = V) -> encode_list(V);
encode_value(V) -> [V].
+-spec encode_key(Key :: binary() | any()) -> atom().
+encode_key(Key) when is_binary(Key) ->
+ binary_to_atom(Key, utf8);
+encode_key(Key) when is_atom(Key) ->
+ Key;
+encode_key(_) -> exit(invalid_key_type).
+
decode_xmerl([#xmlText{value = V, type = text}]) ->
list_to_binary(V);
decode_xmerl(X) when is_list(X) ->
- [{K, decode_xmerl(V)} || #xmlElement{name = K, content = V} <- X].
+ [{atom_to_binary(K, utf8), decode_xmerl(V)} || #xmlElement{name = K, content = V} <- X].
-ifdef(TEST).
encode_single_level_test() ->
@@ -66,4 +76,10 @@ decode_xmerl(X) when is_list(X) ->
, {level2c, <<"value C">>}
, {level2d, <<"value D">>}
]}]).
+
+ encode_binary_key_test() ->
+ {ok, _} = encode([{<<"level1">>, [{<<"level2">>, "value"}]}]).
+
+ decode_test() ->
+ {ok, _} = decode("<?xml version=\"1.0\"?><root><abc>def</abc></root>").
-endif.

0 comments on commit cb87190

Please sign in to comment.