Mutual exclusive: key, keys, start_key and end_key

- Key(s) should incompatible with start_key and end_key
- Treat single element keys as key
diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl
index 9d35e87..8da8bba 100644
--- a/src/couch_mrview/src/couch_mrview_http.erl
+++ b/src/couch_mrview/src/couch_mrview_http.erl
@@ -486,7 +486,12 @@
                 Args0;
             _ ->
                 % group_level set to undefined to detect if explicitly set by user
-                Args0#mrargs{keys = Keys, group = undefined, group_level = undefined}
+                case Keys of
+                    [_] ->
+                        Args0#mrargs{group = undefined, group_level = undefined};
+                    _ ->
+                        Args0#mrargs{keys = Keys, group = undefined, group_level = undefined}
+                end
         end,
     lists:foldl(
         fun({K, V}, Acc) ->
@@ -525,34 +530,147 @@
         "reduce" ->
             Args#mrargs{reduce = parse_boolean(Val)};
         "key" when IsDecoded ->
-            Args#mrargs{start_key = Val, end_key = Val};
+            case Args#mrargs.start_key =:= undefined andalso Args#mrargs.end_key =:= undefined of
+                true ->
+                    Args#mrargs{start_key = Val, end_key = Val};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "key" ->
-            JsonKey = ?JSON_DECODE(Val),
-            Args#mrargs{start_key = JsonKey, end_key = JsonKey};
+            case Args#mrargs.start_key =:= undefined andalso Args#mrargs.end_key =:= undefined of
+                true ->
+                    JsonKey = ?JSON_DECODE(Val),
+                    Args#mrargs{start_key = JsonKey, end_key = JsonKey};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "keys" when IsDecoded ->
-            Args#mrargs{keys = Val};
+            case Val of
+                [Val1] ->
+                    case
+                        Args#mrargs.start_key =:= undefined andalso
+                            Args#mrargs.end_key =:= undefined
+                    of
+                        true ->
+                            Args#mrargs{start_key = Val1, end_key = Val1};
+                        _ ->
+                            throw(
+                                {query_parse_error,
+                                    <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                            )
+                    end;
+                _ ->
+                    Args#mrargs{keys = Val}
+            end;
         "keys" ->
-            Args#mrargs{keys = ?JSON_DECODE(Val)};
+            Val1 = ?JSON_DECODE(Val),
+            case Val1 of
+                [Val2] ->
+                    case
+                        Args#mrargs.start_key =:= undefined andalso
+                            Args#mrargs.end_key =:= undefined
+                    of
+                        true ->
+                            Args#mrargs{start_key = Val2, end_key = Val2};
+                        _ ->
+                            throw(
+                                {query_parse_error,
+                                    <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                            )
+                    end;
+                _ ->
+                    Args#mrargs{keys = Val1}
+            end;
         "startkey" when IsDecoded ->
-            Args#mrargs{start_key = Val};
+            case Args#mrargs.start_key =:= undefined of
+                true ->
+                    Args#mrargs{start_key = Val};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "start_key" when IsDecoded ->
-            Args#mrargs{start_key = Val};
+            case Args#mrargs.start_key =:= undefined of
+                true ->
+                    Args#mrargs{start_key = Val};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "startkey" ->
-            Args#mrargs{start_key = ?JSON_DECODE(Val)};
+            case Args#mrargs.start_key =:= undefined of
+                true ->
+                    Args#mrargs{start_key = ?JSON_DECODE(Val)};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "start_key" ->
-            Args#mrargs{start_key = ?JSON_DECODE(Val)};
+            case Args#mrargs.start_key =:= undefined of
+                true ->
+                    Args#mrargs{start_key = ?JSON_DECODE(Val)};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "startkey_docid" ->
             Args#mrargs{start_key_docid = couch_util:to_binary(Val)};
         "start_key_doc_id" ->
             Args#mrargs{start_key_docid = couch_util:to_binary(Val)};
         "endkey" when IsDecoded ->
-            Args#mrargs{end_key = Val};
+            case Args#mrargs.end_key =:= undefined of
+                true ->
+                    Args#mrargs{end_key = Val};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "end_key" when IsDecoded ->
-            Args#mrargs{end_key = Val};
+            case Args#mrargs.end_key =:= undefined of
+                true ->
+                    Args#mrargs{end_key = Val};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "endkey" ->
-            Args#mrargs{end_key = ?JSON_DECODE(Val)};
+            case Args#mrargs.end_key =:= undefined of
+                true ->
+                    Args#mrargs{end_key = ?JSON_DECODE(Val)};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "end_key" ->
-            Args#mrargs{end_key = ?JSON_DECODE(Val)};
+            case Args#mrargs.end_key =:= undefined of
+                true ->
+                    Args#mrargs{end_key = ?JSON_DECODE(Val)};
+                _ ->
+                    throw(
+                        {query_parse_error,
+                            <<"`key(s)` is incompatible with `start_key` and `end_key`">>}
+                    )
+            end;
         "endkey_docid" ->
             Args#mrargs{end_key_docid = couch_util:to_binary(Val)};
         "end_key_doc_id" ->
diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl
index e1e75f3..208e31d 100644
--- a/src/couch_mrview/src/couch_mrview_util.erl
+++ b/src/couch_mrview/src/couch_mrview_util.erl
@@ -564,7 +564,7 @@
         {red, exact, _} ->
             ok;
         {red, _, KeyList} when is_list(KeyList) ->
-            Msg = <<"Multi-key fetchs for reduce views must use `group=true`">>,
+            Msg = <<"Multi-key fetches for reduce views must use `group=true`">>,
             mrverror(Msg);
         _ ->
             ok
@@ -581,13 +581,12 @@
             ok;
         {[], _, _} ->
             ok;
+        {[Key], StartKey, EndKey} when Key =:= StartKey andalso Key =:= EndKey ->
+            ok;
         {[_ | _], undefined, undefined} ->
             ok;
         _ ->
-            mrverror(<<
-                "`keys` is incompatible with `key`"
-                ", `start_key` and `end_key`"
-            >>)
+            mrverror(<<"`key(s)` is incompatible with `start_key` and `end_key`">>)
     end,
 
     case Args#mrargs.start_key_docid of
diff --git a/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl
index 1a81d4f..0079023 100644
--- a/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl
+++ b/src/couch_mrview/test/eunit/couch_mrview_all_docs_tests.erl
@@ -40,6 +40,7 @@
                 [
                     fun should_query/1,
                     fun should_query_with_range/1,
+                    fun raise_error_query_with_range_and_keys/1,
                     fun should_query_with_range_rev/1,
                     fun should_query_with_limit_and_skip/1,
                     fun should_query_with_include_docs/1,
@@ -79,6 +80,12 @@
         ]},
     ?_assertEqual(Expect, Result).
 
+raise_error_query_with_range_and_keys(Db) ->
+    ?_assertThrow(
+        {query_parse_error, <<"`key(s)` is incompatible with `start_key` and `end_key`">>},
+        run_query(Db, [{keys, [<<"1">>]}, {start_key, <<"5">>}])
+    ).
+
 should_query_with_range_rev(Db) ->
     Result = run_query(Db, [
         {direction, rev},
diff --git a/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl
index bfa4965..e903ab6 100644
--- a/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl
+++ b/src/couch_mrview/test/eunit/couch_mrview_http_tests.erl
@@ -33,5 +33,61 @@
                 undefined,
                 #mrargs{}
             )
+        ),
+
+        ?_assertEqual(
+            #mrargs{start_key = 1, end_key = 1, group_level = undefined, group = undefined},
+            couch_mrview_http:parse_params(
+                [{"key", "1"}],
+                undefined,
+                #mrargs{}
+            )
+        ),
+
+        ?_assertThrow(
+            {query_parse_error, <<"`key(s)` is incompatible with `start_key` and `end_key`">>},
+            couch_mrview_http:parse_params(
+                [{"key", "1"}, {"start_key", "2"}],
+                undefined,
+                #mrargs{}
+            )
+        ),
+
+        ?_assertThrow(
+            {query_parse_error, <<"`key(s)` is incompatible with `start_key` and `end_key`">>},
+            couch_mrview_http:parse_params(
+                [{"end_key", "5"}, {"key", "1"}],
+                undefined,
+                #mrargs{}
+            )
+        ),
+
+        ?_assertThrow(
+            {query_parse_error, <<"`key(s)` is incompatible with `start_key` and `end_key`">>},
+            couch_mrview_http:parse_params(
+                [{"keys", "[1]"}, {"end_key", "5"}],
+                undefined,
+                #mrargs{}
+            )
+        ),
+
+        ?_assertEqual(
+            #mrargs{start_key = 1, end_key = 1, group_level = undefined},
+            couch_mrview_http:parse_params(
+                [{"keys", "[1]"}],
+                undefined,
+                #mrargs{}
+            )
+        ),
+
+        ?_assertEqual(
+            #mrargs{
+                keys = [1, 2], start_key = undefined, end_key = undefined, group_level = undefined
+            },
+            couch_mrview_http:parse_params(
+                [{"keys", "[1, 2]"}],
+                undefined,
+                #mrargs{}
+            )
         )
     ].
diff --git a/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl b/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl
index b6042b6..851ce3b 100644
--- a/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl
+++ b/src/couch_mrview/test/eunit/couch_mrview_red_views_tests.erl
@@ -40,6 +40,7 @@
                 [
                     fun should_reduce_basic/1,
                     fun should_reduce_key_range/1,
+                    fun raise_error_keys_without_group/1,
                     fun should_reduce_with_group_level/1,
                     fun should_reduce_with_group_exact/1
                 ]
@@ -65,6 +66,12 @@
         ]},
     ?_assertEqual(Expect, Result).
 
+raise_error_keys_without_group(Db) ->
+    ?_assertThrow(
+        {query_parse_error, <<"Multi-key fetches for reduce views must use `group=true`">>},
+        run_query(Db, [{keys, [[0, 2], [0, 4]]}])
+    ).
+
 should_reduce_with_group_level(Db) ->
     Result = run_query(Db, [{group_level, 1}]),
     Expect =