|
%% Copyright (c) 2017-Present Pivotal Software, Inc. All rights reserved.
|
|
%%
|
|
%% This package, Looking Glass, is double-licensed under the Mozilla
|
|
%% Public License 1.1 ("MPL") and the Apache License version 2
|
|
%% ("ASL"). For the MPL, please see LICENSE-MPL-RabbitMQ. For the ASL,
|
|
%% please see LICENSE-APACHE2.
|
|
%%
|
|
%% This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
|
|
%% either express or implied. See the LICENSE file for specific language governing
|
|
%% rights and limitations of this software.
|
|
%%
|
|
%% If you have any questions regarding licensing, please contact us at
|
|
%% info@rabbitmq.com.
|
|
|
|
%% Going for hardcoded values for now. We can't spend time
|
|
%% looking up inside a record or map for this.
|
|
|
|
-module(lg_term).
|
|
|
|
-export([truncate/1]).
|
|
-export([truncate/2]).
|
|
|
|
-define(MAX_DEPTH, 5).
|
|
-define(MAX_BINARY_SIZE, 128).
|
|
-define(MAX_BITSTRING_SIZE, ?MAX_BINARY_SIZE * 8).
|
|
-define(MAX_DATA_STRUCTURES, 5).
|
|
-define(MAX_LIST_LENGTH, 32).
|
|
-define(MAX_MAP_SIZE, 32).
|
|
-define(MAX_TUPLE_SIZE, 32).
|
|
|
|
truncate(Term) ->
|
|
truncate(Term, 1).
|
|
|
|
truncate(_, Depth) when Depth > ?MAX_DEPTH ->
|
|
'$truncated';
|
|
truncate(Term, _) when bit_size(Term) > ?MAX_BITSTRING_SIZE ->
|
|
<<Truncated:?MAX_BINARY_SIZE/binary, _/bits>> = Term,
|
|
<<Truncated/binary, "$truncated">>;
|
|
truncate(Term, Depth) when is_list(Term), Depth =:= ?MAX_DEPTH ->
|
|
['$truncated'];
|
|
truncate(Term, Depth) when is_list(Term) ->
|
|
truncate_list(Term, Depth, 0, ?MAX_LIST_LENGTH, 0);
|
|
truncate(Term, Depth) when is_map(Term), Depth =:= ?MAX_DEPTH ->
|
|
#{'$truncated' => '$truncated'};
|
|
truncate(Term, Depth) when is_map(Term) ->
|
|
maps:from_list(truncate_map(maps_to_list(Term, ?MAX_MAP_SIZE), Depth, 0));
|
|
truncate(Term, Depth) when is_tuple(Term), Depth =:= ?MAX_DEPTH ->
|
|
{'$truncated'};
|
|
truncate(Term, Depth) when is_tuple(Term) ->
|
|
list_to_tuple(truncate_list(tuple_to_list(Term), Depth, 0, ?MAX_TUPLE_SIZE, 0));
|
|
truncate(Term, _) ->
|
|
Term.
|
|
|
|
truncate_list([], _, _, _, _) ->
|
|
[];
|
|
truncate_list(_, _, Len, MaxLen, _) when Len > MaxLen ->
|
|
['$truncated'];
|
|
truncate_list(_, _, _, _, NumStructs) when NumStructs > ?MAX_DATA_STRUCTURES ->
|
|
['$truncated'];
|
|
truncate_list([Term|Tail], Depth, Len, MaxLen, NumStructs) ->
|
|
[truncate(Term, Depth + 1)
|
|
%% if List was a cons, Tail can be anything
|
|
|truncate_list(Tail, Depth, Len + 1, MaxLen, NumStructs + is_struct(Term))];
|
|
truncate_list(Term, Depth, _, _, _) -> %% if List was a cons
|
|
truncate(Term, Depth + 1).
|
|
|
|
truncate_map([], _, _) ->
|
|
[];
|
|
truncate_map(_, _, NumStructs) when NumStructs > ?MAX_DATA_STRUCTURES ->
|
|
[{'$truncated', '$truncated'}];
|
|
truncate_map([{Key, Value}|Tail], Depth, NumStructs) ->
|
|
AddStruct = is_struct(Key) + is_struct(Value),
|
|
[{truncate(Key, Depth + 1), truncate(Value, Depth + 1)}
|
|
|truncate_map(Tail, Depth, NumStructs + AddStruct)].
|
|
|
|
is_struct(Term) ->
|
|
if
|
|
is_list(Term) -> 1;
|
|
is_map(Term) -> 1;
|
|
is_tuple(Term) -> 1;
|
|
true -> 0
|
|
end.
|
|
|
|
%% Map iterators were introduced in Erlang/OTP 21. They replace
|
|
%% the undocumented function erts_internal:maps_to_list/2.
|
|
-ifdef(OTP_RELEASE).
|
|
|
|
maps_to_list(Map, MaxSize) ->
|
|
I = maps:iterator(Map),
|
|
maps_to_list(maps:next(I), MaxSize, []).
|
|
|
|
%% Returns elements in arbitrary order. We reverse when we truncate
|
|
%% so that the truncated elements come at the end to avoid having
|
|
%% two truncated elements in the final output.
|
|
maps_to_list(none, _, Acc) ->
|
|
Acc;
|
|
maps_to_list(_, 0, Acc) ->
|
|
lists:reverse([{'$truncated', '$truncated'}|Acc]);
|
|
maps_to_list({K, V, I}, N, Acc) ->
|
|
maps_to_list(maps:next(I), N - 1, [{K, V}|Acc]).
|
|
|
|
-else.
|
|
|
|
maps_to_list(Map, MaxSize) ->
|
|
erts_internal:maps_to_list(Map, MaxSize).
|
|
|
|
-endif.
|
|
|
|
-ifdef(TEST).
|
|
|
|
maps_to_list_test() ->
|
|
[] = maps_to_list(#{}, 10),
|
|
[{'$truncated', '$truncated'}] = maps_to_list(#{a => b}, 0),
|
|
[{a, b}] = maps_to_list(#{a => b}, 10),
|
|
[{a, b}, {c, d}, {e, f}] = lists:sort(maps_to_list(
|
|
#{a => b, c => d, e => f}, 3)),
|
|
[{'$truncated', '$truncated'}, {a, b}, {c, d}, {e, f}] = lists:sort(maps_to_list(
|
|
#{a => b, c => d, e => f, g => h}, 3)),
|
|
[{'$truncated', '$truncated'}, {a, b}, {c, d}, {e, f}] = lists:sort(maps_to_list(
|
|
#{a => b, c => d, e => f, g => h, i => j}, 3)),
|
|
%% Confirm that truncated values are at the end.
|
|
[_, _, _, {'$truncated', '$truncated'}] = maps_to_list(
|
|
#{a => b, c => d, e => f, g => h, i => j}, 3),
|
|
ok.
|
|
|
|
-endif.
|