blob: 087bab3fc2ddbcf9959aef1e756d2c52d56384d4 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(mem3_bdu).
-export([
before_doc_update/3
]).
-include_lib("couch/include/couch_db.hrl").
-spec before_doc_update(#doc{}, Db :: any(), couch_db:update_type()) -> #doc{}.
before_doc_update(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, _Db, _UpdateType) ->
% Skip design docs
Doc;
before_doc_update(#doc{deleted = true} = Doc, _Db, _UpdateType) ->
% Skip deleted
Doc;
before_doc_update(#doc{} = Doc, _Db, ?REPLICATED_CHANGES) ->
% Skip internal replicator updates
Doc;
before_doc_update(#doc{} = Doc, _Db, _UpdateType) ->
Body1 = couch_util:json_encode(Doc#doc.body),
Body2 = couch_util:json_decode(Body1, [return_maps]),
validate(Body2),
Doc.
validate(#{} = Body) ->
validate_key(<<"by_node">>, Body, ["by_node is mandatory"]),
validate_key(<<"by_range">>, Body, ["by_range is mandatory"]),
ByNode = maps:get(<<"by_node">>, Body),
case is_map(ByNode) of
true -> ok;
false -> throw({forbidden, ["by_node not an object"]})
end,
ByRange = maps:get(<<"by_range">>, Body),
case is_map(ByRange) of
true -> ok;
false -> throw({forbidden, ["by_range not an object"]})
end,
% "by_node": {
% "node1@xxx.xxx.xxx.xxx": ["00000000-1fffffff",...]
% ]}
maps:map(
fun(Node, Ranges) ->
validate_by_node(Node, Ranges, ByRange)
end,
ByNode
),
% "by_range": {
% "00000000-1fffffff": ["node1@xxx.xxx.xxx.xxx", ...]
% ]}
maps:map(
fun(Range, Nodes) ->
validate_by_range(Range, Nodes, ByNode)
end,
ByRange
).
validate_by_node(Node, Ranges, ByRange) ->
validate_array(Ranges, ["by_node", Ranges, "value not an array"]),
lists:foreach(
fun(Range) ->
validate_key(Range, ByRange, ["by_range for", Range, "missing"]),
Nodes = maps:get(Range, ByRange),
validate_member(Node, Nodes, ["by_range for", Range, "missing", Node])
end,
Ranges
).
validate_by_range(Range, Nodes, ByNode) ->
validate_array(Nodes, ["by_range", Nodes, "value not an array"]),
lists:foreach(
fun(Node) ->
validate_key(Node, ByNode, ["by_node for", Node, "missing"]),
Ranges = maps:get(Node, ByNode),
validate_member(Range, Ranges, ["by_node for", Node, "missing", Range])
end,
Nodes
).
validate_array(Val, _ErrMsg) when is_list(Val) ->
ok;
validate_array(_Val, ErrMsg) ->
throw({forbidden, errmsg(ErrMsg)}).
validate_key(Key, #{} = Map, ErrMsg) ->
case maps:is_key(Key, Map) of
true -> ok;
false -> throw({forbidden, errmsg(ErrMsg)})
end.
validate_member(Val, Array, ErrMsg) when is_list(Array) ->
case lists:member(Val, Array) of
true -> ok;
false -> throw({forbidden, errmsg(ErrMsg)})
end;
validate_member(_Val, _Array, ErrMsg) ->
throw({forbidden, errmsg(ErrMsg)}).
errmsg(ErrMsg) when is_list(ErrMsg) ->
list_to_binary(lists:join(" ", ErrMsg)).