Extract query validation callback step to custom validator

This commit is contained in:
Liam 2024-07-09 13:26:26 -04:00
parent ade92481f8
commit c89424d146
4 changed files with 77 additions and 24 deletions

View file

@ -1,9 +1,10 @@
defmodule Philomena.Filters.Filter do defmodule Philomena.Filters.Filter do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import PhilomenaQuery.Ecto.QueryValidator
alias Philomena.Schema.TagList alias Philomena.Schema.TagList
alias Philomena.Schema.Search alias Philomena.Images.Query
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Repo alias Philomena.Repo
@ -48,8 +49,8 @@ defmodule Philomena.Filters.Filter do
|> validate_required([:name]) |> validate_required([:name])
|> validate_my_downvotes(:spoilered_complex_str) |> validate_my_downvotes(:spoilered_complex_str)
|> validate_my_downvotes(:hidden_complex_str) |> validate_my_downvotes(:hidden_complex_str)
|> Search.validate_search(:spoilered_complex_str, user) |> validate_query(:spoilered_complex_str, &Query.compile(&1, user: user))
|> Search.validate_search(:hidden_complex_str, user) |> validate_query(:hidden_complex_str, &Query.compile(&1, user: user))
|> unsafe_validate_unique([:user_id, :name], Repo) |> unsafe_validate_unique([:user_id, :name], Repo)
end end

View file

@ -1,18 +0,0 @@
defmodule Philomena.Schema.Search do
alias Philomena.Images.Query
alias PhilomenaQuery.Parse.String
import Ecto.Changeset
def validate_search(changeset, field, user, watched \\ false) do
query = changeset |> get_field(field) |> String.normalize()
output = Query.compile(query, user: user, watch: watched)
case output do
{:ok, _} ->
changeset
_ ->
add_error(changeset, field, "is invalid")
end
end
end

View file

@ -4,10 +4,11 @@ defmodule Philomena.Users.User do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import PhilomenaQuery.Ecto.QueryValidator
alias Philomena.Schema.TagList alias Philomena.Schema.TagList
alias Philomena.Schema.Search
alias Philomena.Images.Query
alias Philomena.Filters.Filter alias Philomena.Filters.Filter
alias Philomena.ArtistLinks.ArtistLink alias Philomena.ArtistLinks.ArtistLink
alias Philomena.Badges alias Philomena.Badges
@ -354,8 +355,8 @@ defmodule Philomena.Users.User do
|> validate_inclusion(:images_per_page, 1..50) |> validate_inclusion(:images_per_page, 1..50)
|> validate_inclusion(:comments_per_page, 1..100) |> validate_inclusion(:comments_per_page, 1..100)
|> validate_inclusion(:scale_large_images, ["false", "partscaled", "true"]) |> validate_inclusion(:scale_large_images, ["false", "partscaled", "true"])
|> Search.validate_search(:watched_images_query_str, user, true) |> validate_query(:watched_images_query_str, &Query.compile(&1, user: user, watch: true))
|> Search.validate_search(:watched_images_exclude_str, user, true) |> validate_query(:watched_images_exclude_str, &Query.compile(&1, user: user, watch: true))
end end
def description_changeset(user, attrs) do def description_changeset(user, attrs) do

View file

@ -0,0 +1,69 @@
defmodule PhilomenaQuery.Ecto.QueryValidator do
@moduledoc """
Query string validation for Ecto.
It enables the following usage pattern by taking a fn of the compiler:
defmodule Filter do
import PhilomenaQuery.Ecto.QueryValidator
# ...
def changeset(filter, attrs, user) do
filter
|> cast(attrs, [:complex])
|> validate_required([:complex])
|> validate_query([:complex], with: &Query.compile(&1, user: user))
end
end
"""
import Ecto.Changeset
alias PhilomenaQuery.Parse.String
@doc """
Validates a query string using the provided attribute(s) and compiler.
Returns the changeset as-is, or with an `"is invalid"` error added to validated field.
## Examples
# With single attribute
filter
|> cast(attrs, [:complex])
|> validate_query(:complex, &Query.compile(&1, user: user))
# With list of attributes
filter
|> cast(attrs, [:spoilered_complex, :hidden_complex])
|> validate_query([:spoilered_complex, :hidden_complex], &Query.compile(&1, user: user))
"""
def validate_query(changeset, attr_or_attr_list, callback)
def validate_query(changeset, attr_list, callback) when is_list(attr_list) do
Enum.reduce(attr_list, changeset, fn attr, changeset ->
validate_query(changeset, attr, callback)
end)
end
def validate_query(changeset, attr, callback) do
if changed?(changeset, attr) do
validate_assuming_changed(changeset, attr, callback)
else
changeset
end
end
defp validate_assuming_changed(changeset, attr, callback) do
with value when is_binary(value) <- fetch_change!(changeset, attr) || "",
value <- String.normalize(value),
{:ok, _} <- callback.(value) do
changeset
else
_ ->
add_error(changeset, attr, "is invalid")
end
end
end