Просмотр исходного кода

feat(tpl): add separate `placeholders/1` function

The purpose is to have a clearer view of placeholders used in a
template, without going the usual `render(Template, #{})` route that is
actually subtly misleading: it won't mention that `${}` / `${.}`
placeholder has been used.

Also unify handling of `${}` / `${.}` in a couple of places.
Andrew Mayorov 1 год назад
Родитель
Сommit
fb0da9848c
2 измененных файлов с 48 добавлено и 5 удалено
  1. 30 5
      apps/emqx_utils/src/emqx_template.erl
  2. 18 0
      apps/emqx_utils/test/emqx_template_SUITE.erl

+ 30 - 5
apps/emqx_utils/src/emqx_template.erl

@@ -20,6 +20,7 @@
 -export([parse/2]).
 -export([parse_deep/1]).
 -export([parse_deep/2]).
+-export([placeholders/1]).
 -export([validate/2]).
 -export([is_const/1]).
 -export([unparse/1]).
@@ -143,14 +144,19 @@ parse_accessor(Var) ->
             Name
     end.
 
+-spec placeholders(t()) -> [varname()].
+placeholders(Template) when is_list(Template) ->
+    [Name || {var, Name, _} <- Template];
+placeholders({'$tpl', Template}) ->
+    placeholders_deep(Template).
+
 %% @doc Validate a template against a set of allowed variables.
 %% If the given template contains any variable not in the allowed set, an error
 %% is returned.
 -spec validate([varname() | {var_namespace, varname()}], t()) ->
     ok | {error, [_Error :: {varname(), disallowed}]}.
 validate(Allowed, Template) ->
-    {_, Errors} = render(Template, #{}),
-    {Used, _} = lists:unzip(Errors),
+    Used = placeholders(Template),
     case find_disallowed(lists:usort(Used), Allowed) of
         [] ->
             ok;
@@ -192,10 +198,13 @@ is_allowed(Var, [{var_namespace, VarPrefix} | Allowed]) ->
         false ->
             is_allowed(Var, Allowed)
     end;
-is_allowed(Var, [Var | _Allowed]) ->
+is_allowed(Var, [VarAllowed | Rest]) ->
+    is_same_varname(Var, VarAllowed) orelse is_allowed(Var, Rest).
+
+is_same_varname("", ".") ->
     true;
-is_allowed(Var, [_ | Allowed]) ->
-    is_allowed(Var, Allowed).
+is_same_varname(V1, V2) ->
+    V1 =:= V2.
 
 %% @doc Check if a template is constant with respect to rendering, i.e. does not
 %% contain any placeholders.
@@ -322,6 +331,22 @@ parse_deep_term(Term, Opts) when is_binary(Term) ->
 parse_deep_term(Term, _Opts) ->
     Term.
 
+-spec placeholders_deep(deeptpl()) -> [varname()].
+placeholders_deep(Template) when is_map(Template) ->
+    maps:fold(
+        fun(KT, VT, Acc) -> placeholders_deep(KT) ++ placeholders_deep(VT) ++ Acc end,
+        [],
+        Template
+    );
+placeholders_deep({list, Template}) when is_list(Template) ->
+    lists:flatmap(fun placeholders_deep/1, Template);
+placeholders_deep({tuple, Template}) when is_list(Template) ->
+    lists:flatmap(fun placeholders_deep/1, Template);
+placeholders_deep(Template) when is_list(Template) ->
+    placeholders(Template);
+placeholders_deep(_Term) ->
+    [].
+
 render_deep(Template, Context, Opts) when is_map(Template) ->
     maps:fold(
         fun(KT, VT, {Acc, Errors}) ->

+ 18 - 0
apps/emqx_utils/test/emqx_template_SUITE.erl

@@ -128,6 +128,14 @@ t_render_custom_bindings(_) ->
         render_string(Template, {?MODULE, []})
     ).
 
+t_placeholders(_) ->
+    TString = <<"a:${a},b:${b},c:$${c},d:{${d.d1}},e:${$}{e},lit:${$}{$}">>,
+    Template = emqx_template:parse(TString),
+    ?assertEqual(
+        ["a", "b", "c", "d.d1"],
+        emqx_template:placeholders(Template)
+    ).
+
 t_unparse(_) ->
     TString = <<"a:${a},b:${b},c:$${c},d:{${d.d1}},e:${$}{e},lit:${$}{$}">>,
     Template = emqx_template:parse(TString),
@@ -337,6 +345,16 @@ t_unparse_tmpl_deep(_) ->
     Template = emqx_template:parse_deep(Term),
     ?assertEqual(Term, emqx_template:unparse(Template)).
 
+t_allow_this(_) ->
+    ?assertEqual(
+        {error, [{"", disallowed}]},
+        emqx_template:validate(["d"], emqx_template:parse(<<"this:${}">>))
+    ),
+    ?assertEqual(
+        {error, [{"", disallowed}]},
+        emqx_template:validate(["d"], emqx_template:parse(<<"this:${.}">>))
+    ).
+
 t_allow_var_by_namespace(_) ->
     Context = #{d => #{d1 => <<"hi">>}},
     Template = emqx_template:parse(<<"d.d1:${d.d1}">>),