You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2015/09/21 20:57:15 UTC
[1/2] couch-mrview commit: updated refs/heads/master to dfa6f7e
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 <va...@gmail.com>
Authored: Mon Sep 21 14:07:03 2015 -0400
Committer: Nick Vatamaniuc <va...@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, [])).
[2/2] couch-mrview commit: updated refs/heads/master to dfa6f7e
Posted by rn...@apache.org.
fix unused variable warning
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/dfa6f7ee
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/tree/dfa6f7ee
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/diff/dfa6f7ee
Branch: refs/heads/master
Commit: dfa6f7eeef9752e45ed88a825e98b1222988ac47
Parents: 7691f48
Author: Robert Newson <rn...@apache.org>
Authored: Mon Sep 21 19:54:48 2015 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Mon Sep 21 19:54:48 2015 +0100
----------------------------------------------------------------------
src/couch_mrview.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch-mrview/blob/dfa6f7ee/src/couch_mrview.erl
----------------------------------------------------------------------
diff --git a/src/couch_mrview.erl b/src/couch_mrview.erl
index 63064c6..bd866a2 100644
--- a/src/couch_mrview.erl
+++ b/src/couch_mrview.erl
@@ -58,7 +58,7 @@
%% of "lib" is an object that will contain libary code
%% so it not validated.
%%
-validate_view(<<"lib">>, {Libs})->
+validate_view(<<"lib">>, {_Libs})->
ok;
validate_view(VName, {Views}) ->
case couch_util:get_value(<<"map">>, Views) of