This is an automated email from the ASF dual-hosted git repository. iilyak pushed a commit to branch couch-stats-resource-tracker-v3-rebase-http-2 in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 568baf186fb912180c0df3bb5b8af6328efc2989 Author: ILYA Khlopotov <iil...@apache.org> AuthorDate: Fri Jun 27 15:46:39 2025 -0700 Fix encoding of JSON reply --- .../src/couch_stats_resource_tracker.hrl | 5 +- src/couch_stats/src/csrt_httpd.erl | 111 +++++++++++++++++---- 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/src/couch_stats/src/couch_stats_resource_tracker.hrl b/src/couch_stats/src/couch_stats_resource_tracker.hrl index aba38c16e..9b52826c7 100644 --- a/src/couch_stats/src/couch_stats_resource_tracker.hrl +++ b/src/couch_stats/src/couch_stats_resource_tracker.hrl @@ -194,4 +194,7 @@ -type query_options() :: #{limit => pos_integer()}. -type aggregation_key() :: tuple_of_field_names(). -type aggregation_values() :: tuple_of_field_values(). --type query_result() :: #{aggregation_key() => non_neg_integer()}. +-type aggregation_result() :: #{aggregation_key() => non_neg_integer()}. +-type ordered_result() :: [{aggregation_key(), non_neg_integer()}]. +-type query_result() :: aggregation_result() | ordered_result(). +-type json_spec(_Spec) :: term(). diff --git a/src/couch_stats/src/csrt_httpd.erl b/src/couch_stats/src/csrt_httpd.erl index 3fb4f0212..562e1a450 100644 --- a/src/couch_stats/src/csrt_httpd.erl +++ b/src/couch_stats/src/csrt_httpd.erl @@ -37,7 +37,9 @@ resp_to_json([], Acc) -> % %% TODO: incorporate Bad responses % Resp = rpc_to_json(csrt:rpc(active, [json])), % send_json(Req, Resp); -handle_resource_status_req(#httpd{method = 'POST', path_parts = [<<"_active_resources">>, <<"_match">>, MatcherName]} = Req) -> +handle_resource_status_req( + #httpd{method = 'POST', path_parts = [<<"_active_resources">>, <<"_match">>, MatcherName]} = Req +) -> chttpd:validate_ctype(Req, "application/json"), {JsonProps} = chttpd:json_body_obj(Req), GroupBy = couch_util:get_value(<<"group_by">>, JsonProps), @@ -46,11 +48,11 @@ handle_resource_status_req(#httpd{method = 'POST', path_parts = [<<"_active_reso case {GroupBy, SortBy, CountBy} of {undefined, undefined, {Query}} -> - handle_count_by(Req, MatcherName, Query); + handle_count_by(Req, binary_to_list(MatcherName), Query); {undefined, {Query}, undefined} -> - handle_sort_by(Req, MatcherName, Query); + handle_sort_by(Req, binary_to_list(MatcherName), Query); {{Query}, undefined, undefined} -> - handle_group_by(Req, MatcherName, Query); + handle_group_by(Req, binary_to_list(MatcherName), Query); {_, _, _} -> throw({bad_request, <<"Multiple aggregations are not supported">>}) end; @@ -66,10 +68,9 @@ handle_count_by(Req, MatcherName, CountBy) -> AggregationKeys = couch_util:get_value(<<"aggregate_keys">>, CountBy), case csrt:query_count_by(MatcherName, AggregationKeys) of {ok, Map} -> - send_json(Req, {aggregation_result_to_json(Map)}); - Else -> - %% TODO handle error - throw({bad_request, Else}) + send_json(Req, aggregation_result_to_json(AggregationKeys, Map)); + {error, Reason} -> + send_error(Req, Reason) end. handle_sort_by(Req, MatcherName, SortBy) -> @@ -77,10 +78,10 @@ handle_sort_by(Req, MatcherName, SortBy) -> CounterKey = couch_util:get_value(<<"counter_key">>, SortBy), case csrt:query_sort_by(MatcherName, AggregationKeys, CounterKey) of {ok, Map} -> - send_json(Req, {aggregation_result_to_json(Map)}); - Else -> - %% TODO handle error - throw({bad_request, Else}) + io:format(user, "Map ~p~n", [Map]), + send_json(Req, aggregation_result_to_json(AggregationKeys, Map)); + {error, Reason} -> + send_error(Req, Reason) end. handle_group_by(Req, MatcherName, GroupBy) -> @@ -88,16 +89,82 @@ handle_group_by(Req, MatcherName, GroupBy) -> CounterKey = couch_util:get_value(<<"counter_key">>, GroupBy), case csrt:query_group_by(MatcherName, AggregationKeys, CounterKey) of {ok, Map} -> - send_json(Req, {aggregation_result_to_json(Map)}); - Else -> - %% TODO handle error - throw({bad_request, Else}) + send_json(Req, aggregation_result_to_json(AggregationKeys, Map)); + {error, Reason} -> + send_error(Req, Reason) end. -aggregation_result_to_json(Map) when is_map(Map) -> - maps:fold(fun(K, V, Acc) -> [{key_to_string(K), V} | Acc] end, [], Map). +%% [{{<<"user_foo">>,<<"eunit-test-db-3950c6fc68955a4b629cebbece5bdfac">>}, 10}] +% -type aggregation_result() :: #{aggregation_key() => non_neg_integer()}. +% -type ordered_result() :: [{aggregation_key(), non_neg_integer()}]. +% -type query_result() :: aggregation_result() | ordered_result(). -key_to_string(Key) when is_tuple(Key) -> - list_to_binary(string:join([atom_to_list(K) || K <- tuple_to_list(Key)], ",")); -key_to_string(Key) when is_atom(Key) -> - atom_to_binary(Key). +encode_key(AggregationKeys, Key) -> + maps:from_list(lists:zip(AggregationKeys, tuple_to_list(Key))). + +-spec aggregation_result_to_json(AggregationKeys :: binary() | [binary()], Map :: query_result()) -> + json_spec(#{ + value => non_neg_integer(), + key => #{ + username => string(), + dbname => string() + } + }). + +aggregation_result_to_json(AggregationKeys, Map) when + is_map(Map) andalso is_list(AggregationKeys) +-> + maps:fold( + fun(K, V, Acc) -> + [ + #{ + value => V, + key => encode_key(AggregationKeys, K) + } + | Acc + ] + end, + [], + Map + ); +aggregation_result_to_json(AggregationKey, Map) when + is_map(Map) andalso is_binary(AggregationKey) +-> + maps:fold( + fun(K, V, Acc) -> + [ + #{value => V, key => #{AggregationKey => K}} | Acc + ] + end, + [], + Map + ); +aggregation_result_to_json(AggregationKeys, Ordered) when + is_list(Ordered) andalso is_list(AggregationKeys) +-> + lists:map( + fun({K, V}) -> + #{ + value => V, + key => encode_key(AggregationKeys, K) + } + end, + Ordered + ); +aggregation_result_to_json(AggregationKey, Ordered) when + is_list(Ordered) andalso is_binary(AggregationKey) +-> + lists:map( + fun({K, V}) -> + #{value => V, key => #{AggregationKey => K}} + end, + Ordered + ). + +send_error(Req, {unknown_matcher, Matcher}) -> + MatcherBin = list_to_binary(Matcher), + chttpd:send_error(Req, {bad_request, <<"Unknown matcher '", MatcherBin/binary, "'">>}); +send_error(Req, {invalid_key, FieldName}) -> + chttpd:send_error(Req, {bad_request, <<"Unknown field name '", FieldName/binary, "'">>}); +send_error(Req, Reason) -> + chttpd:send_error(Req, {error, Reason}). \ No newline at end of file