Current section
Files
Jump to
Current section
Files
src/ejsonpath_transform.erl
-module(ejsonpath_transform).
-ifdef(TEST).
-compile([export_all, nowarn_export_all]).
-endif.
% -define(EJSONPATH_DEBUG, true).
-include("./ejsonpath.hrl").
-import(ejsonpath_common, [argument/2, argument/3, script_eval/3]).
-export([
transform/5
]).
transform({root, '$'}, Node, Transform, _, _) ->
case Transform({match, to_arg(argument(Node, "$"))}) of
{error, Error} -> erlang:error(Error);
{ok, Result} -> {Result, ["$"]};
delete -> erlang:error(not_supported);
Invalid -> erlang:error({badreturn, Invalid})
end;
transform({root, Predicates}, Node, Transform, Funcs, Options) ->
Context = #{
root => Node,
opts => Options,
funcs => Funcs,
transform => Transform,
eval_root => fun (SubQuery) ->
ejsonpath_eval:eval(SubQuery, Node, Funcs, Options)
end,
eval_step => fun (SubQuery, CurrNode, Ctx) ->
ejsonpath_eval:eval_step(SubQuery, [CurrNode], Ctx)
end
},
transform_step(Predicates, argument(Node, "$"), Context).
% {key, '*'}
transform_step([{child, {predicate, {key, '*'}}} | Rest], #argument{type = hash, node = Node, path = _Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, key, '*', _Path}),
Keys = maps:keys(Node),
transform_step([{child, {predicate, {access_list, Keys}}}] ++ Rest, Arg, Ctx);
transform_step([{child, {predicate, {key, '*'}}} | Rest], #argument{type = array, node = Node, path = _Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, key, '*', _Path}),
Idxs = lists:seq(0, erlang:length(Node)-1),
transform_step([{child, {predicate, {access_list, Idxs}}}] ++ Rest, Arg, Ctx);
% {key, Key}
transform_step([{child, {predicate, {key, Key}}} | Rest], #argument{type = hash, path = Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, key, Key, Path}),
apply_transform([Key], Arg, fun(_, Value) ->
transform_step(Rest, argument(Value, Path, Key), Ctx)
end, Ctx, Rest /= []);
% {access_list, Keys}
transform_step([{child, {predicate, {access_list, Keys}}} | Rest], #argument{path = Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, access_list, Keys, Path}),
apply_transform(Keys, Arg, fun(Key, Value) ->
transform_step(Rest, argument(Value, Path, Key), Ctx)
end, Ctx, Rest /= []);
% {filter_expr, Script}
transform_step([{child, {predicate, {filter_expr, Script}}} | Rest], #argument{type = hash, node = Node, path = Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, filter_expr, hash, Path, Script}),
Keys = lists:reverse(maps:fold(fun (Key, Value, Acc) ->
case ejsonpath_common:to_boolean(script_eval(Script, argument(Value, Path, Key), Ctx)) of
false -> Acc;
_ -> [Key|Acc]
end
end, [], Node)),
case Keys of
[] -> erlang:error(not_found);
_ ->
apply_transform(Keys, Arg, fun(Key, Value) ->
transform_step(Rest, argument(Value, Path, Key), Ctx)
end, Ctx, Rest /= [])
end;
transform_step([{child, {predicate, {filter_expr, Script}}}|Rest], #argument{type = array, node = Node, path = Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, filter_expr, array, Path, Script}),
{_, Idxs} = lists:foldl(fun (Item, {Idx, Acc}) ->
case ejsonpath_common:to_boolean(script_eval(Script, argument(Item, Path, Idx), Ctx)) of
false -> {Idx+1, Acc};
_ -> {Idx+1, [Idx|Acc]}
end
end, {0, []}, Node),
case Idxs of
[] -> erlang:error(not_found);
_ ->
apply_transform(lists:reverse(Idxs), Arg, fun(Idx, Value) ->
transform_step(Rest, argument(Value, Path, Idx), Ctx)
end, Ctx, Rest /= [])
end;
transform_step([{child, {predicate, {filter_expr, Script}}} | Rest], #argument{path = _Path} = Arg, Ctx) ->
?EJSONPATH_LOG({enter, filter_expr, _Path, Script}),
case ejsonpath_common:to_boolean(script_eval(Script, Arg, Ctx)) of
false -> erlang:error(not_found);
_ -> transform_step(Rest, Arg, Ctx)
end;
%% {slice, S, E, Step}
transform_step([{child, {predicate, {slice, Start, End, Step}}} | Rest], #argument{type = array, node = Node} = Arg, Ctx) ->
?EJSONPATH_LOG({slice, Start, End, Step}),
case ejsonpath_common:slice_seq(Start, End, Step, length(Node)) of
{error, _} -> [];
Seq ->
transform_step([{child, {predicate, {access_list, Seq}}}] ++ Rest, Arg, Ctx)
end;
transform_step([], #argument{node = _Node, path = Path} = Arg, #{transform := Transform}) ->
?EJSONPATH_LOG({match, _Node, Path}),
case Transform({match, to_arg(Arg)}) of
{error, Error} -> erlang:error(Error);
{ok, Result} -> {Result, [Path]};
delete -> {'$delete', [Path]};
Invalid -> erlang:error({badreturn, Invalid})
end;
transform_step(_Pr, _A, _) ->
?EJSONPATH_LOG({not_implemented, _Pr, _A}),
erlang:error(not_implemented).
apply_transform(Keys, #argument{type = hash, node = Acc0, path = Path} = Arg, Func, Ctx, HaveOngoingQuery) ->
lists:foldl(fun (Key, {Acc, Paths}) ->
case maps:get(Key, Acc, '$undefined') of
'$undefined' ->
case HaveOngoingQuery of
true -> erlang:error(not_found);
false ->
% we handle not_found event only if last query item
%% if no more query and item doesn't exists
PathToBe = ejsonpath_common:buildpath(Key, Path),
NotFoundHandler = notfound_func(Ctx, fun(_) -> erlang:error(not_found) end),
case NotFoundHandler({not_found, PathToBe, Key, to_arg(Arg)}) of
{error, Err} -> erlang:error(Err);
{ok, NewAcc} -> {NewAcc, Paths ++ [PathToBe]};
% on not_found and result is delete command
% we return the current node (without the key)
delete -> {Acc, Paths ++ [PathToBe]};
Invalid -> erlang:error(Invalid)
end
end;
Value ->
case Func(Key, Value) of
{'$delete', NewPaths} ->
{maps:remove(Key, Acc), Paths ++ NewPaths};
{NewValue, NewPaths} ->
{maps:put(Key, NewValue, Acc), Paths ++ NewPaths}
end
end
end, {Acc0, []}, Keys);
apply_transform(Idxs, #argument{type = array, node = Acc0, path = Path} = Arg, Func, Ctx, HaveOngoingQuery) ->
{NewNode, NewPaths} = lists:foldl(fun (Idx0, {Acc, Paths}) ->
case ejsonpath_common:index(Idx0, erlang:length(Acc)) of
{error, _} ->
case HaveOngoingQuery of
true -> erlang:error(not_found);
false ->
PathToBe = ejsonpath_common:buildpath(Idx0, Path),
NotFoundHandler = notfound_func(Ctx, fun(_) -> erlang:error(not_found) end),
case NotFoundHandler({not_found, PathToBe, Idx0, to_arg(Arg)}) of
{error, Err} -> erlang:error(Err);
{ok, NewAcc} -> {NewAcc, Paths ++ [PathToBe]};
delete -> {Acc, Paths ++ [PathToBe]};
Invalid -> erlang:error(Invalid)
end
end;
{ok, Idx1} ->
case Func(Idx0, lists:nth(Idx1, Acc)) of
{'$delete', NewPaths } ->
{ejsonpath_common:insert_list(Idx1, '$deletion_marker', Acc), Paths ++ NewPaths};
{Value, NewPaths} ->
{ejsonpath_common:insert_list(Idx1, Value, Acc), Paths ++ NewPaths}
end
end
end, {Acc0, []}, Idxs),
{lists:filter(fun (X) -> X /= '$deletion_marker' end, NewNode), NewPaths};
apply_transform(_,_,_,_,_) ->
erlang:error(not_found).
to_arg(#argument{type = Type, node = Node, path = Path}) ->
#{ type => Type, node => Node, path => Path}.
notfound_func(#{opts := Options, transform := Transform}, DefaultFunc) ->
case proplists:get_value(handle_not_found, Options, DefaultFunc) of
true -> Transform;
Func when is_function(Func) -> Func;
_Invalid -> erlang:error(badarg)
end.