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:
2025-05-28 14:40:15 -03:00
parent 6b9c66c385
commit 4a9cb34f9c
8 changed files with 318 additions and 78 deletions

View 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

View 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