couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rnew...@apache.org
Subject [1/2] couch-mrview commit: updated refs/heads/master to dfa6f7e
Date Mon, 21 Sep 2015 18:57:15 GMT
Repository: couchdb-couch-mrview
Updated Branches:
  refs/heads/master 95cbd7b2a -> dfa6f7eee


Improve design document validation

Jira: COUCHDB-2818

Validates top level values:

  - These should be object: "options", "filters", "lists",
    "shows", "updates", "views"

  - "rewrites" is an array

  - "language" and "validate_doc_update" are strings

Values in "filters", "lists", "shows", "updates" should be strings.
They are mappings from function names to function contents.

Values in "options" can be anything (currently there are 2 boolean
options, but they are not checked specifically).

Views are special:

  - "lib" is special: its value will be an object that
    contains mapping to different libraries. The contents of
    that object is not validated.

  - If any other object has "map" or "reduce" value, it should
    be a string.

Also try to produce helpful messages for user so they know where
the problem is.

Some examples of responses:

```
$ http -phb PUT $DB1/db2/_design/des1 views:='{"v1":"nope"}'
HTTP/1.1 400 Bad Request
{
    "error": "invalid_design_doc", "reason": "View v1 must be an object"
}

$ http -phb PUT $DB1/db2/_design/des1 lists:='{"l1":true}'
HTTP/1.1 400 Bad Request
{
    "error": "invalid_design_doc", "reason": "`l1` in lists is not a string"
}

$ http -phb PUT $DB1/db2/_design/des1 views:='{"v1": {"map":1}}'
HTTP/1.1 400 Bad Request
{
    "error": "invalid_design_doc", "reason": "`map` in v1 must be a string"
}
```


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/commit/7691f486
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/tree/7691f486
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/diff/7691f486

Branch: refs/heads/master
Commit: 7691f48627ebac932f41e0ac2d7748e979673883
Parents: 95cbd7b
Author: Nick Vatamaniuc <vatamane@gmail.com>
Authored: Mon Sep 21 14:07:03 2015 -0400
Committer: Nick Vatamaniuc <vatamane@gmail.com>
Committed: Mon Sep 21 14:07:03 2015 -0400

----------------------------------------------------------------------
 src/couch_mrview.erl                        | 104 ++++++++-
 test/couch_mrview_ddoc_validation_tests.erl | 283 ++++++++++++++++++++++-
 2 files changed, 378 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/blob/7691f486/src/couch_mrview.erl
----------------------------------------------------------------------
diff --git a/src/couch_mrview.erl b/src/couch_mrview.erl
index 819f580..63064c6 100644
--- a/src/couch_mrview.erl
+++ b/src/couch_mrview.erl
@@ -46,16 +46,104 @@
 }).
 
 
-validate(DbName, DDoc) ->
-    {Fields} = DDoc#doc.body,
-    case couch_util:get_value(<<"options">>, Fields, {[]}) of
-        {_} -> ok;
-        _ -> throw({invalid_design_doc, <<"`options` parameter must be an object.">>})
+
+%% Validate "views" name : value mapping.
+%%
+%% In the most common case name is the view name and
+%% value is an object that must have a "map" function,
+%% and an optional "reduce" function. "map" and "reduce"
+%% values should be strings (function contents).
+%%
+%% "lib" is a special case. The value
+%% of "lib" is an object that will contain libary code
+%% so it not validated.
+%%
+validate_view(<<"lib">>, {Libs})->
+    ok;
+validate_view(VName, {Views}) ->
+    case couch_util:get_value(<<"map">>, Views) of
+        MapVal when is_binary(MapVal) ->
+            ok;
+        undefined ->
+            Err0 = <<"View ",VName/binary, " must have a map function">>,
+            throw ({invalid_design_doc, Err0});
+        _ ->
+            Err1 = <<"`map` in ", VName/binary, " must be a string">>,
+            throw({invalid_design_doc, Err1})
     end,
-    case couch_util:get_value(<<"views">>, Fields, {[]}) of
-        {_} -> ok;
-        _ -> throw({invalid_design_doc, <<"`views` parameter must be an object.">>})
+    case couch_util:get_value(<<"reduce">>, Views) of
+        RedVal when is_binary(RedVal) ->
+            ok;
+        undefined ->
+            ok; % note: unlike map,  reduce function is optional
+        _ ->
+            Err2 = <<"`reduce` in ", VName/binary, " must be a string">>,
+            throw({invalid_design_doc, Err2})
     end,
+    ok;
+validate_view(VName, _) ->
+    throw({invalid_design_doc, <<"View ", VName/binary, " must be an object">>}).
+
+
+%% Validate top level design document objects.
+%%
+%% Most cases (shows,lists,filters,...) will be
+%% function_name : function_contents(string) mappings.
+%% For example, lists can look like:
+%% {
+%%    "lst1" : "function(head, req){...}",
+%%    "lst2" : "function(head, req){...}"
+%% }
+%%
+%% With 2 excpetions:
+%%  - views :  Validated by a special function.
+%%  - options : Allowed to contain non-string values.
+%%
+validate_opt_object(<<"views">>, {Views})->
+    [validate_view(VName, ViewObj) || {VName, ViewObj} <- Views],
+    ok;
+validate_opt_object(<<"options">>, {_})->
+    ok;
+validate_opt_object(Section, {Fields}) ->
+    [validate_opt_string(FName, FVal, Section) || {FName, FVal} <- Fields],
+    ok;
+validate_opt_object(_, undefined) ->
+    ok;
+validate_opt_object(FieldName, _) ->
+    throw({invalid_design_doc, <<"`", FieldName/binary, "` is not an object">>}).
+
+
+%% If this field value is present, it must be a string
+validate_opt_string(FName, FVal)->
+    validate_opt_string(FName, FVal, <<"design doc">>).
+
+validate_opt_string(_, FVal, _) when is_binary(FVal) ->
+    ok;
+validate_opt_string(_, undefined, _) ->
+    ok;
+validate_opt_string(FName, _, Section) ->
+    ErrMsg = <<"`", FName/binary, "` in ", Section/binary, " is not a string">>,
+    throw({invalid_design_doc, ErrMsg}).
+
+
+%% If this field is present it, must be an array
+validate_opt_array(_, ArrVal) when is_list(ArrVal) ->
+    ok;
+validate_opt_array(_, undefined) ->
+    ok;
+validate_opt_array(FieldName, _) ->
+    throw({invalid_design_doc, <<"`", FieldName/binary, "` is not an array">>}).
+
+
+validate(DbName,  DDoc) ->
+    {Fields} = DDoc#doc.body,
+    ObjFields =  [<<"options">>, <<"filters">>, <<"lists">>,
+                  <<"shows">>, <<"updates">>, <<"views">>],
+    StringFields = [<<"language">>, <<"validate_doc_update">>],
+    ArrayFields = [<<"rewrites">>],
+    [validate_opt_object(F, couch_util:get_value(F, Fields)) || F <- ObjFields],
+    [validate_opt_string(F, couch_util:get_value(F, Fields)) || F <- StringFields],
+    [validate_opt_array(F, couch_util:get_value(F, Fields))  || F <- ArrayFields],
     GetName = fun
         (#mrview{map_names = [Name | _]}) -> Name;
         (#mrview{reduce_funs = [{Name, _} | _]}) -> Name;

http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/blob/7691f486/test/couch_mrview_ddoc_validation_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_mrview_ddoc_validation_tests.erl b/test/couch_mrview_ddoc_validation_tests.erl
index 89ae138..1717616 100644
--- a/test/couch_mrview_ddoc_validation_tests.erl
+++ b/test/couch_mrview_ddoc_validation_tests.erl
@@ -36,7 +36,34 @@ ddoc_validation_test_() ->
                 [
                     fun should_reject_invalid_js_map/1,
                     fun should_reject_invalid_js_reduce/1,
-                    fun should_reject_invalid_builtin_reduce/1
+                    fun should_reject_invalid_builtin_reduce/1,
+                    fun should_reject_non_object_options/1,
+                    fun should_reject_non_object_filters/1,
+                    fun should_reject_non_object_lists/1,
+                    fun should_reject_non_object_shows/1,
+                    fun should_reject_non_object_updates/1,
+                    fun should_reject_non_object_views/1,
+                    fun should_reject_non_string_language/1,
+                    fun should_reject_non_string_validate_doc_update/1,
+                    fun should_reject_non_array_rewrites/1,
+                    fun should_accept_option/1,
+                    fun should_accept_any_option/1,
+                    fun should_accept_filter/1,
+                    fun should_reject_non_string_filter_function/1,
+                    fun should_accept_list/1,
+                    fun should_reject_non_string_list_function/1,
+                    fun should_accept_show/1,
+                    fun should_reject_non_string_show_function/1,
+                    fun should_accept_update/1,
+                    fun should_reject_non_string_update_function/1,
+                    fun should_accept_view/1,
+                    fun should_accept_view_with_reduce/1,
+                    fun should_accept_view_with_lib/1,
+                    fun should_reject_view_that_is_not_an_object/1,
+                    fun should_reject_view_without_map_function/1,
+                    fun should_reject_view_with_non_string_map_function/1,
+                    fun should_reject_view_with_non_string_reduce_function/1,
+                    fun should_accept_any_in_lib/1
                 ]
             }
         }
@@ -82,3 +109,257 @@ should_reject_invalid_builtin_reduce(Db) ->
     ?_assertThrow(
         {bad_request, invalid_design_doc, _},
         couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_options(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_options">>},
+        {<<"options">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`options` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_filters(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_filters">>},
+        {<<"filters">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`filters` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_lists(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_lists">>},
+        {<<"lists">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`lists` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_shows(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_shows">>},
+        {<<"shows">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`shows` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_updates(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_updates">>},
+        {<<"updates">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`updates` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_object_views(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_views">>},
+        {<<"views">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`views` is not an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_language(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_language">>},
+        {<<"language">>, 1}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`language` in design doc is not a string">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_validate_doc_update(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_vdu">>},
+        {<<"validate_doc_update">>, 1}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`validate_doc_update` in design doc is not a string">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_array_rewrites(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_array_rewrites">>},
+        {<<"rewrites">>, <<"invalid">>}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                   <<"`rewrites` is not an array">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_option(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_options">>},
+        {<<"options">>, {[ {<<"option1">>, <<"function(doc,req){}">>}
]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_accept_any_option(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_any_option">>},
+        {<<"options">>, {[ {<<"option1">>, true} ]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_accept_filter(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_filters">>},
+        {<<"filters">>, {[ {<<"filter1">>, <<"function(doc,req){}">>}
]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_filter_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_filter_function">>},
+        {<<"filters">>, {[ {<<"filter1">>, 1} ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`filter1` in filters is not a string">> },
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_list(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_lists">>},
+        {<<"lists">>, {[ {<<"list1">>, <<"function(doc,req){}">>}
]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_list_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_list_function">>},
+        {<<"lists">>, {[ {<<"list1">>, 1} ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`list1` in lists is not a string">> },
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_show(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_shows">>},
+        {<<"shows">>, {[ {<<"show1">>, <<"function(doc,req){}">>}
]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_show_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_show_function">>},
+        {<<"shows">>, {[ {<<"show1">>, 1} ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`show1` in shows is not a string">> },
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_update(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_updates">>},
+        {<<"updates">>, {[ {<<"update1">>, <<"function(doc,req){}">>}
]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_reject_non_string_update_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_string_update_function">>},
+        {<<"updates">>, {[ {<<"update1">>, 1} ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`update1` in updates is not a string">> },
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_view(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_view">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[{<<"map">>, <<"function(d){}">>}]}}
+                       ]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_accept_view_with_reduce(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_view_with_reduce">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[
+                                         {<<"map">>, <<"function(d){}">>},
+                                         {<<"reduce">>,<<"function(d){}">>}
+                                        ]}}
+                       ]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_accept_view_with_lib(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_view_with_lib">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[
+                                         {<<"map">>, <<"function(d){}">>}
+                                        ]}},
+                         {<<"lib">>, {[
+                                         {<<"lib1">>, <<"x=42">>}
+                                      ]}}
+                       ]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).
+
+should_reject_view_that_is_not_an_object(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_non_object_view">>},
+        {<<"views">>, {[{<<"view1">>, <<"thisisbad">>}]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"View view1 must be an object">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_view_without_map_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_view_without_map">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[]}}
+                       ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"View view1 must have a map function">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+
+should_reject_view_with_non_string_map_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_view_with_nonstr_map">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[
+                                         {<<"map">>,{[]}}
+                                        ]}}
+                       ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`map` in view1 must be a string">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_reject_view_with_non_string_reduce_function(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_reject_view_with_nonstr_reduce">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[
+                                         {<<"map">>,<<"function(d){}">>},
+                                         {<<"reduce">>,1}
+                                        ]}}
+                       ]}}
+    ]}),
+    ?_assertThrow({bad_request,invalid_design_doc,
+                  <<"`reduce` in view1 must be a string">>},
+                  couch_db:update_doc(Db, Doc, [])).
+
+should_accept_any_in_lib(Db) ->
+    Doc = couch_doc:from_json_obj({[
+        {<<"_id">>, <<"_design/should_accept_any_in_lib">>},
+        {<<"views">>, {[
+                         {<<"view1">>, {[
+                                         {<<"map">>, <<"function(d){}">>}
+                                        ]}},
+                         {<<"lib">>, {[{<<"lib1">>, {[]}}]}}
+                       ]}}
+    ]}),
+    ?_assertMatch({ok,_}, couch_db:update_doc(Db, Doc, [])).


Mime
View raw message