Skip to content

Commit 7f8f999

Browse files
committed
[wip] return failure report from mango VDU on PUT /db/doc
1 parent c8ad7dc commit 7f8f999

4 files changed

Lines changed: 134 additions & 32 deletions

File tree

src/couch/src/couch_query_servers.erl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,8 @@ validate_doc_update(Db, DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
481481
case Resp of
482482
ok ->
483483
ok;
484+
{[{<<"forbidden">>, Message}, {<<"failures">>, Failures}]} ->
485+
throw({forbidden, Message, Failures});
484486
{[{<<"forbidden">>, Message}]} ->
485487
throw({forbidden, Message});
486488
{[{<<"unauthorized">>, Message}]} ->

src/mango/src/mango_native_proc.erl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,14 @@ handle_call({prompt, [<<"ddoc">>, DDocId, [<<"validate_doc_update">>], Args]}, _
115115
[NewDoc, OldDoc, _Ctx, _SecObj] = Args,
116116
Struct = {[{<<"newDoc">>, NewDoc}, {<<"oldDoc">>, OldDoc}]},
117117
Reply =
118-
case mango_selector:match(Selector, Struct) of
119-
true -> true;
120-
_ -> {[{<<"forbidden">>, <<"document is not valid">>}]}
118+
case mango_selector:match_failures(Selector, Struct) of
119+
[] ->
120+
true;
121+
Failures ->
122+
{[
123+
{<<"forbidden">>, <<"forbidden">>},
124+
{<<"failures">>, Failures}
125+
]}
121126
end,
122127
{reply, Reply, St}
123128
end;

src/mango/src/mango_selector.erl

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ match(Selector, D) ->
7070

7171
match_failures(Selector, D) ->
7272
couch_stats:increment_counter([mango, evaluate_selector]),
73-
match_int(Selector, D, true).
73+
[format_failure(F) || F <- match_int(Selector, D, true)].
7474

7575
match_int(Selector, D) ->
7676
match_int(Selector, D, false).
@@ -575,8 +575,8 @@ match({[{<<"$keyMapMatch">>, Arg}]}, Value, #ctx{path = Path} = Ctx) when is_tup
575575
true -> [];
576576
false -> lists:flatten(KeyFailures)
577577
end;
578-
match({[{<<"$keyMapMatch">>, _Arg}]}, _Value, Ctx) ->
579-
[#failure{op = keyMapMatch, type = bad_value, ctx = Ctx}];
578+
match({[{<<"$keyMapMatch">>, _Arg}]}, Value, Ctx) ->
579+
[#failure{op = keyMapMatch, type = bad_value, params = [Value], ctx = Ctx}];
580580
% Our comparison operators are fairly straight forward
581581
match({[{<<"$lt">>, Arg}]}, Value, #ctx{cmp = Cmp} = Ctx) ->
582582
compare(lt, Arg, Ctx, Cmp(Value, Arg) < 0);
@@ -712,6 +712,86 @@ compare(Op, Arg, #ctx{negate = Neg} = Ctx, Cond) ->
712712
_ -> [#failure{op = Op, params = [Arg], ctx = Ctx}]
713713
end.
714714

715+
format_failure(#failure{op = Op, type = Type, params = Params, ctx = Ctx}) ->
716+
Path = format_path(Ctx#ctx.path),
717+
Msg = format_op(Op, Ctx#ctx.negate, Type, Params),
718+
{[{<<"path">>, Path}, {<<"message">>, list_to_binary(Msg)}]}.
719+
720+
format_op(Op, _, empty_list, _) ->
721+
io_lib:format("operator $~p was invoked with an empty list", [Op]);
722+
format_op(Op, _, bad_value, [Value]) ->
723+
io_lib:format("operator $~p was invoked with a bad value: ~s", [Op, jiffy:encode(Value)]);
724+
format_op(field, _, not_found, []) ->
725+
io_lib:format("must be present", []);
726+
format_op(eq, false, mismatch, [X]) ->
727+
io_lib:format("must be equal to ~s", [jiffy:encode(X)]);
728+
format_op(ne, false, mismatch, [X]) ->
729+
io_lib:format("must not be equal to ~s", [jiffy:encode(X)]);
730+
format_op(lt, false, mismatch, [X]) ->
731+
io_lib:format("must be less than ~s", [jiffy:encode(X)]);
732+
format_op(lte, false, mismatch, [X]) ->
733+
io_lib:format("must be less than or equal to ~s", [jiffy:encode(X)]);
734+
format_op(gt, false, mismatch, [X]) ->
735+
io_lib:format("must be greater than ~s", [jiffy:encode(X)]);
736+
format_op(gte, false, mismatch, [X]) ->
737+
io_lib:format("must be greater than or equal to ~s", [jiffy:encode(X)]);
738+
format_op(in, false, mismatch, [X]) ->
739+
io_lib:format("must be one of ~s", [jiffy:encode(X)]);
740+
format_op(nin, false, mismatch, [X]) ->
741+
io_lib:format("must not be one of ~s", [jiffy:encode(X)]);
742+
format_op(all, false, mismatch, [X]) ->
743+
io_lib:format("must contain all the values in ~s", [jiffy:encode(X)]);
744+
format_op(exists, false, mismatch, [true]) ->
745+
io_lib:format("must be present", []);
746+
format_op(exists, false, mismatch, [false]) ->
747+
io_lib:format("must not be present", []);
748+
format_op(type, false, mismatch, [Type]) ->
749+
io_lib:format("must be of type '~s'", [Type]);
750+
format_op(type, true, mismatch, [Type]) ->
751+
io_lib:format("must not be of type '~s'", [Type]);
752+
format_op(mod, false, mismatch, [D, R]) ->
753+
io_lib:format("must leave a remainder of ~p when divided by ~p", [R, D]);
754+
format_op(mod, true, mismatch, [D, R]) ->
755+
io_lib:format("must leave a remainder other than ~p when divided by ~p", [R, D]);
756+
format_op(regex, false, mismatch, [P]) ->
757+
io_lib:format("must match the pattern '~s'", [P]);
758+
format_op(regex, true, mismatch, [P]) ->
759+
io_lib:format("must not match the pattern '~s'", [P]);
760+
format_op(beginsWith, false, mismatch, [P]) ->
761+
io_lib:format("must begin with '~s'", [P]);
762+
format_op(beginsWith, true, mismatch, [P]) ->
763+
io_lib:format("must not begin with '~s'", [P]);
764+
format_op(size, false, mismatch, [N]) ->
765+
io_lib:format("must contain ~p items", [N]);
766+
format_op(size, true, mismatch, [N]) ->
767+
io_lib:format("must not contain ~p items", [N]);
768+
format_op(eq, true, Type, Params) ->
769+
format_op(ne, false, Type, Params);
770+
format_op(ne, true, Type, Params) ->
771+
format_op(eq, false, Type, Params);
772+
format_op(lt, true, Type, Params) ->
773+
format_op(gte, false, Type, Params);
774+
format_op(lte, true, Type, Params) ->
775+
format_op(gt, false, Type, Params);
776+
format_op(gt, true, Type, Params) ->
777+
format_op(lte, false, Type, Params);
778+
format_op(gte, true, Type, Params) ->
779+
format_op(le, false, Type, Params);
780+
format_op(in, true, Type, Params) ->
781+
format_op(nin, false, Type, Params);
782+
format_op(nin, true, Type, Params) ->
783+
format_op(in, false, Type, Params);
784+
format_op(exists, true, Type, [Exist]) ->
785+
format_op(exists, false, Type, [not Exist]).
786+
787+
format_path([]) ->
788+
[];
789+
format_path([Item | Rest]) when is_binary(Item) ->
790+
{ok, Path} = mango_util:parse_field(Item),
791+
format_path(Rest) ++ Path;
792+
format_path([Item | Rest]) when is_integer(Item) ->
793+
format_path(Rest) ++ [list_to_binary(integer_to_list(Item))].
794+
715795
% Returns true if Selector requires all
716796
% fields in RequiredFields to exist in any matching documents.
717797

@@ -1869,33 +1949,36 @@ match_failures_object_test() ->
18691949
]}
18701950
),
18711951

1872-
Fails0 = match_failures(
1952+
Fails0 = match_int(
18731953
Selector,
18741954
{[
18751955
{<<"a">>, 1},
18761956
{<<"b">>, {[{<<"c">>, 3}]}}
1877-
]}
1957+
]},
1958+
true
18781959
),
18791960
?assertEqual([], Fails0),
18801961

1881-
Fails1 = match_failures(
1962+
Fails1 = match_int(
18821963
Selector,
18831964
{[
18841965
{<<"a">>, 0},
18851966
{<<"b">>, {[{<<"c">>, 3}]}}
1886-
]}
1967+
]},
1968+
true
18871969
),
18881970
?assertMatch(
18891971
[#failure{op = eq, type = mismatch, params = [1], ctx = #ctx{path = [<<"a">>]}}],
18901972
Fails1
18911973
),
18921974

1893-
Fails2 = match_failures(
1975+
Fails2 = match_int(
18941976
Selector,
18951977
{[
18961978
{<<"a">>, 1},
18971979
{<<"b">>, {[{<<"c">>, 4}]}}
1898-
]}
1980+
]},
1981+
true
18991982
),
19001983
?assertMatch(
19011984
[#failure{op = eq, type = mismatch, params = [3], ctx = #ctx{path = [<<"b.c">>]}}],
@@ -1912,21 +1995,21 @@ match_failures_elemmatch_test() ->
19121995
]}
19131996
),
19141997

1915-
Fails0 = match_failures(
1916-
SelElemMatch, {[{<<"a">>, [5, 3, 2]}]}
1998+
Fails0 = match_int(
1999+
SelElemMatch, {[{<<"a">>, [5, 3, 2]}]}, true
19172000
),
19182001
?assertEqual([], Fails0),
19192002

1920-
Fails1 = match_failures(
1921-
SelElemMatch, {[{<<"a">>, []}]}
2003+
Fails1 = match_int(
2004+
SelElemMatch, {[{<<"a">>, []}]}, true
19222005
),
19232006
?assertMatch(
19242007
[#failure{op = elemMatch, type = empty_list, params = [], ctx = #ctx{path = [<<"a">>]}}],
19252008
Fails1
19262009
),
19272010

1928-
Fails2 = match_failures(
1929-
SelElemMatch, {[{<<"a">>, [3, 2]}]}
2011+
Fails2 = match_int(
2012+
SelElemMatch, {[{<<"a">>, [3, 2]}]}, true
19302013
),
19312014
?assertMatch(
19322015
[
@@ -1946,21 +2029,21 @@ match_failures_allmatch_test() ->
19462029
]}
19472030
),
19482031

1949-
Fails0 = match_failures(
1950-
SelAllMatch, {[{<<"a">>, [5]}]}
2032+
Fails0 = match_int(
2033+
SelAllMatch, {[{<<"a">>, [5]}]}, true
19512034
),
19522035
?assertEqual([], Fails0),
19532036

1954-
Fails1 = match_failures(
1955-
SelAllMatch, {[{<<"a">>, [4]}]}
2037+
Fails1 = match_int(
2038+
SelAllMatch, {[{<<"a">>, [4]}]}, true
19562039
),
19572040
?assertMatch(
19582041
[#failure{op = gt, type = mismatch, params = [4], ctx = #ctx{path = [0, <<"a">>]}}],
19592042
Fails1
19602043
),
19612044

1962-
Fails2 = match_failures(
1963-
SelAllMatch, {[{<<"a">>, [5, 6, 3, 7, 0]}]}
2045+
Fails2 = match_int(
2046+
SelAllMatch, {[{<<"a">>, [5, 6, 3, 7, 0]}]}, true
19642047
),
19652048
?assertMatch(
19662049
[
@@ -1980,13 +2063,13 @@ match_failures_allmatch_object_test() ->
19802063
]}
19812064
),
19822065

1983-
Fails0 = match_failures(
1984-
SelAllMatch, {[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 5}]}]}]}}]}
2066+
Fails0 = match_int(
2067+
SelAllMatch, {[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 5}]}]}]}}]}, true
19852068
),
19862069
?assertEqual([], Fails0),
19872070

1988-
Fails1 = match_failures(
1989-
SelAllMatch, {[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 4}]}]}]}}]}
2071+
Fails1 = match_int(
2072+
SelAllMatch, {[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 4}]}]}]}}]}, true
19902073
),
19912074
?assertMatch(
19922075
[
@@ -1997,9 +2080,10 @@ match_failures_allmatch_object_test() ->
19972080
Fails1
19982081
),
19992082

2000-
Fails2 = match_failures(
2083+
Fails2 = match_int(
20012084
SelAllMatch,
2002-
{[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 5}]}, {[{<<"c">>, 6}]}, {[{<<"c">>, 3}]}]}]}}]}
2085+
{[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 5}]}, {[{<<"c">>, 6}]}, {[{<<"c">>, 3}]}]}]}}]},
2086+
true
20032087
),
20042088
?assertMatch(
20052089
[
@@ -2010,9 +2094,10 @@ match_failures_allmatch_object_test() ->
20102094
Fails2
20112095
),
20122096

2013-
Fails3 = match_failures(
2097+
Fails3 = match_int(
20142098
SelAllMatch,
2015-
{[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 1}]}, {[]}]}]}}]}
2099+
{[{<<"a">>, {[{<<"b">>, [{[{<<"c">>, 1}]}, {[]}]}]}}]},
2100+
true
20162101
),
20172102
?assertMatch(
20182103
[
@@ -2029,6 +2114,13 @@ match_failures_allmatch_object_test() ->
20292114
Fails3
20302115
).
20312116

2117+
format_path_test() ->
2118+
?assertEqual([], format_path([])),
2119+
?assertEqual([<<"a">>], format_path([<<"a">>])),
2120+
?assertEqual([<<"a">>, <<"b">>], format_path([<<"b">>, <<"a">>])),
2121+
?assertEqual([<<"a">>, <<"b">>, <<"c">>], format_path([<<"b.c">>, <<"a">>])),
2122+
?assertEqual([<<"a">>, <<"42">>, <<"b">>, <<"c">>], format_path([<<"b.c">>, 42, <<"a">>])).
2123+
20322124
bench(Name, Selector, Doc) ->
20332125
Sel1 = normalize(Selector),
20342126
[Normal, Verbose] = erlperf:compare(

test/elixir/test/validate_doc_update_test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ defmodule ValidateDocUpdateTest do
105105
resp = Couch.put("/#{db}/doc", body: %{"no" => "type"})
106106
assert resp.status_code == 403
107107
assert resp.body["error"] == "forbidden"
108+
assert resp.body["reason"] == [
109+
%{"path" => ["newDoc", "type"], "message" => "must be present"}
110+
]
108111
end
109112

110113
@tag :with_db

0 commit comments

Comments
 (0)