| 1 |
|
defmodule Tools.User.Validate do |
| 2 |
|
import Code, only: [string_to_quoted: 1] |
| 3 |
|
import Enum, only: [map: 2, reduce: 3, find: 2, member?: 2, all?: 2] |
| 4 |
|
import String, only: [split: 2, trim: 1] |
| 5 |
|
import Tools.User.Ast, only: [bound: 1, refs: 1] |
| 6 |
|
|
| 7 |
|
def check(tool) do |
| 8 |
|
tool[:code] |
| 9 |
|
|> process(tool[:params], tool[:name]) |
| 10 |
10 |
|> unwrap(tool) |
| 11 |
|
end |
| 12 |
|
|
| 13 |
:-( |
defp process([], _, _), do: :ok |
| 14 |
:-( |
defp process(nil, _, _), do: :ok |
| 15 |
10 |
defp process(_, nil, _), do: :ok |
| 16 |
:-( |
defp process(_, "", _), do: :ok |
| 17 |
|
|
| 18 |
|
defp process(code, params, name) do |
| 19 |
:-( |
allowed = split(params, ",") |> map(&trim/1) |
| 20 |
:-( |
reduce(code, :ok, &fold(&1, allowed, name, &2)) |
| 21 |
|
end |
| 22 |
|
|
| 23 |
:-( |
defp fold(_code, _allowed, _name, {:error, _} = err), do: err |
| 24 |
|
|
| 25 |
|
defp fold(code, allowed, name, :ok) do |
| 26 |
:-( |
code |> string_to_quoted() |> scan(allowed, name) |
| 27 |
|
end |
| 28 |
|
|
| 29 |
:-( |
defp scan({:ok, ast}, allowed, name), do: vet(ast, allowed, name) |
| 30 |
:-( |
defp scan({:error, _}, _, _), do: :ok |
| 31 |
|
|
| 32 |
|
defp vet(ast, allowed, name) do |
| 33 |
:-( |
bad = find(refs(ast), &bad?(&1, allowed, bound(ast))) |
| 34 |
:-( |
resolve(bad, name) |
| 35 |
|
end |
| 36 |
|
|
| 37 |
|
defp bad?(var, allowed, bound) do |
| 38 |
:-( |
[allowed, bound] |> all?(&skip?(&1, var)) |
| 39 |
|
end |
| 40 |
|
|
| 41 |
:-( |
defp skip?(list, var), do: not member?(list, var) |
| 42 |
|
|
| 43 |
:-( |
defp resolve(nil, _), do: :ok |
| 44 |
:-( |
defp resolve(undefined, name), do: {:error, name, undefined} |
| 45 |
|
|
| 46 |
10 |
defp unwrap(:ok, tool), do: tool |
| 47 |
|
|
| 48 |
|
defp unwrap({:error, name, var}, _tool) do |
| 49 |
:-( |
raise RuntimeError, "#{name} undefined variable #{var}" |
| 50 |
|
end |
| 51 |
|
end |