Se crea el parser para customfilter. Agregamos variables de entorno para manejar url de adjuntos con o sin clave de encriptacion.
This commit is contained in:
124
lib/api/parser/expression_parser.ex
Normal file
124
lib/api/parser/expression_parser.ex
Normal file
@ -0,0 +1,124 @@
|
||||
defmodule ExpressionParser do
|
||||
import NimbleParsec
|
||||
|
||||
whitespace = ignore(optional(ascii_string([?\s, ?\t, ?\n, ?\r], min: 1)))
|
||||
|
||||
identifier =
|
||||
ascii_string([?a..?z, ?A..?Z, ?_, ?0..?9], min: 1)
|
||||
|> label("identifier")
|
||||
|> unwrap_and_tag(:field)
|
||||
|
||||
quoted_string =
|
||||
ignore(string("\""))
|
||||
|> repeat(utf8_char(not: ?"))
|
||||
|> reduce({List, :to_string, []})
|
||||
|> ignore(string("\""))
|
||||
|
||||
number =
|
||||
ascii_string([?0..?9], min: 1)
|
||||
|> map({String, :to_integer, []})
|
||||
|
||||
atom_value =
|
||||
ignore(string("'"))
|
||||
|> ascii_string([?a..?z, ?A..?Z], min: 1)
|
||||
|> ignore(string("'"))
|
||||
|> map({__MODULE__, :identity, []})
|
||||
|
||||
list_item =
|
||||
choice([
|
||||
number,
|
||||
atom_value,
|
||||
quoted_string |> unwrap_and_tag(:string)
|
||||
])
|
||||
|
||||
atom_list =
|
||||
ignore(string("["))
|
||||
|> optional(whitespace)
|
||||
|> repeat(
|
||||
list_item
|
||||
|> optional(ignore(string(",")))
|
||||
|> optional(whitespace)
|
||||
)
|
||||
|> ignore(string("]"))
|
||||
|> tag(:list)
|
||||
|
||||
|
||||
|
||||
operator =
|
||||
choice([
|
||||
string("ilike") |> replace(:ilike),
|
||||
string(">=") |> replace(:>=),
|
||||
string("<=") |> replace(:<=),
|
||||
string("!=") |> replace(:!=),
|
||||
string(">") |> replace(:>),
|
||||
string("<") |> replace(:<),
|
||||
string("=") |> replace(:==),
|
||||
string("in") |> replace(:in)
|
||||
])
|
||||
|
||||
value =
|
||||
choice([
|
||||
quoted_string |> unwrap_and_tag(:string),
|
||||
number |> unwrap_and_tag(:number),
|
||||
atom_list
|
||||
])
|
||||
|
||||
comparison =
|
||||
identifier
|
||||
|> ignore(whitespace)
|
||||
|> concat(operator)
|
||||
|> ignore(whitespace)
|
||||
|> concat(value)
|
||||
|> tag(:comparison)
|
||||
|
||||
defcombinatorp :expr, parsec(:logic_expr)
|
||||
|
||||
paren_expr =
|
||||
ignore(string("("))
|
||||
|> ignore(whitespace)
|
||||
|> parsec(:expr)
|
||||
|> ignore(whitespace)
|
||||
|> ignore(string(")"))
|
||||
|
||||
logic_term =
|
||||
choice([
|
||||
paren_expr,
|
||||
comparison
|
||||
])
|
||||
|
||||
logical_op =
|
||||
ignore(whitespace)
|
||||
|> choice([
|
||||
string("and") |> replace(:and),
|
||||
string("or") |> replace(:or)
|
||||
])
|
||||
|> ignore(whitespace)
|
||||
|
||||
logic_expr =
|
||||
logic_term
|
||||
|> repeat(
|
||||
logical_op
|
||||
|> concat(logic_term)
|
||||
)
|
||||
|> reduce({__MODULE__, :reduce_logic, []})
|
||||
|
||||
defparsec :logic_expr, logic_expr
|
||||
|
||||
def parse(input) do
|
||||
case logic_expr(input) do
|
||||
{:ok, result, "", _, _, _} -> {:ok, result}
|
||||
{:ok, _, rest, _, _, _} -> {:error, "Unexpected remaining input: #{inspect(rest)}"}
|
||||
{:error, reason, _, _, _, _} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
def reduce_logic([head | tail]) do
|
||||
Enum.chunk_every(tail, 2)
|
||||
|> Enum.reduce(head, fn [op, right], acc ->
|
||||
{op, acc, right}
|
||||
end)
|
||||
end
|
||||
|
||||
def identity(value), do: value
|
||||
|
||||
end
|
59
lib/api/parser/expression_to_ecto.ex
Normal file
59
lib/api/parser/expression_to_ecto.ex
Normal file
@ -0,0 +1,59 @@
|
||||
defmodule ExpressionToEcto do
|
||||
import Ecto.Query
|
||||
|
||||
def to_dynamic([]), do: true
|
||||
|
||||
def to_dynamic([single]), do: build_dynamic(single)
|
||||
|
||||
def to_dynamic(ast), do: build_dynamic(ast)
|
||||
|
||||
# Operadores lógicos
|
||||
defp build_dynamic({:or, left, right}) do
|
||||
dynamic([q], ^build_dynamic(left) or ^build_dynamic(right))
|
||||
end
|
||||
|
||||
defp build_dynamic({:and, left, right}) do
|
||||
dynamic([q], ^build_dynamic(left) and ^build_dynamic(right))
|
||||
end
|
||||
|
||||
# Comparaciones con número
|
||||
defp build_dynamic({:comparison, [{:field, field}, op, {:number, val}]}) do
|
||||
field_atom = String.to_atom(field)
|
||||
|
||||
case op do
|
||||
:== -> dynamic([q], field(q, ^field_atom) == ^val)
|
||||
:!= -> dynamic([q], field(q, ^field_atom) != ^val)
|
||||
:> -> dynamic([q], field(q, ^field_atom) > ^val)
|
||||
:< -> dynamic([q], field(q, ^field_atom) < ^val)
|
||||
:>= -> dynamic([q], field(q, ^field_atom) >= ^val)
|
||||
:<= -> dynamic([q], field(q, ^field_atom) <= ^val)
|
||||
other -> raise "Operador no soportado: #{inspect(other)}"
|
||||
end
|
||||
end
|
||||
|
||||
# Comparaciones con string
|
||||
defp build_dynamic({:comparison, [{:field, field}, op, {:string, val}]}) when op in [:==, :!=] do
|
||||
field_atom = String.to_atom(field)
|
||||
case op do
|
||||
:== -> dynamic([q], field(q, ^field_atom) == ^val)
|
||||
:!= -> dynamic([q], field(q, ^field_atom) != ^val)
|
||||
end
|
||||
end
|
||||
|
||||
# ILIKE
|
||||
defp build_dynamic({:comparison, [{:field, field}, :ilike, {:string, val}]}) do
|
||||
field_atom = String.to_atom(field)
|
||||
pattern = "#{val}"
|
||||
dynamic([q], ilike(field(q, ^field_atom), ^pattern))
|
||||
end
|
||||
|
||||
# IN
|
||||
defp build_dynamic({:comparison, [{:field, field}, :in, {:list, vals}]}) do
|
||||
field_atom = String.to_atom(field)
|
||||
dynamic([q], field(q, ^field_atom) in ^vals)
|
||||
end
|
||||
|
||||
defp build_dynamic(other) do
|
||||
raise "AST no soportado o mal formado: #{inspect(other)}"
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user