diff --git a/lib/frenzy/pipeline/filter_stage.ex b/lib/frenzy/pipeline/filter_stage.ex index 6c537ba..f18a511 100644 --- a/lib/frenzy/pipeline/filter_stage.ex +++ b/lib/frenzy/pipeline/filter_stage.ex @@ -32,6 +32,72 @@ defmodule Frenzy.Pipeline.FilterStage do {:ok, item_params} end + @impl Stage + def validate_opts(opts) when is_map(opts) do + cond do + not (Map.has_key?(opts, "mode") and is_binary(opts["mode"]) and + opts["mode"] in ["accept", "reject"]) -> + {:error, "mode must be a string, either 'accept' or 'reject'"} + + not (Map.has_key?(opts, "score") and is_integer(opts["score"])) -> + {:error, "score must be an integer"} + + not (Map.has_key?(opts, "rules") and is_list(opts["rules"])) -> + {:error, "rules must be a list of rules"} + + true -> + validate_rules(opts) + end + end + + @impl Stage + def validate_opts(_opts), do: {:error, "options must be a map"} + + defp validate_rules(%{"rules" => rules} = opts) do + rules + |> Enum.with_index() + |> Enum.reduce_while(:ok, fn {rule, index}, :ok -> + case validate_rule(rule) do + :ok -> + {:cont, :ok} + + {:error, reason} -> + {:halt, {:error, "invalid rule #{index}: #{reason}"}} + end + end) + |> case do + :ok -> + {:ok, opts} + + {:error, _reason} = err -> + err + end + end + + defp validate_rule(rule) do + cond do + not is_map(rule) -> + {:error, "rule must be a map"} + + not (Map.has_key?(rule, "mode") and is_binary(rule["mode"]) and + rule["mode"] in ["contains_string", "matches_regex"]) -> + {:error, "mode property must be a string, either 'contains_string' or 'matches_regex'"} + + not (Map.has_key?(rule, "property") and is_binary(rule["property"]) and + rule["property"] in ["url", "title", "author"]) -> + {:error, "property property must be a string, either 'url', 'title', or 'author'"} + + not (Map.has_key?(rule, "param") and is_binary(rule["param"])) -> + {:error, "param property must be a string"} + + not (Map.has_key?(rule, "weight") and is_integer(rule["weight"])) -> + {:error, "weight property must be an integer"} + + true -> + :ok + end + end + defp test( %{"mode" => mode, "property" => property, "param" => param, "weight" => weight}, item_params diff --git a/lib/frenzy/pipeline/scrape_stage.ex b/lib/frenzy/pipeline/scrape_stage.ex index d29d833..4e85cc7 100644 --- a/lib/frenzy/pipeline/scrape_stage.ex +++ b/lib/frenzy/pipeline/scrape_stage.ex @@ -15,6 +15,9 @@ defmodule Frenzy.Pipeline.ScrapeStage do end end + @impl Stage + def validate_opts(opts), do: {:ok, opts} + defp get_article_content(url) when is_binary(url) and url != "" do Logger.debug("Getting article from #{url}") diff --git a/lib/frenzy/pipeline/stage.ex b/lib/frenzy/pipeline/stage.ex index e1208a0..9eb6067 100644 --- a/lib/frenzy/pipeline/stage.ex +++ b/lib/frenzy/pipeline/stage.ex @@ -1,3 +1,5 @@ defmodule Frenzy.Pipeline.Stage do @callback apply(Map.t(), Map.t()) :: {:ok, Map.t()} | :tombstone | {:error, String.t()} + + @callback validate_opts(Map.t()) :: {:ok, Map.t()} | {:error, String.t()} end diff --git a/lib/frenzy_web/controllers/pipeline_controller.ex b/lib/frenzy_web/controllers/pipeline_controller.ex index e0d1950..6f10dd5 100644 --- a/lib/frenzy_web/controllers/pipeline_controller.ex +++ b/lib/frenzy_web/controllers/pipeline_controller.ex @@ -65,7 +65,11 @@ defmodule FrenzyWeb.PipelineController do feed = conn.assigns[:feed] stage = conn.assigns[:stage] - with {:ok, options} <- Jason.decode(options_json) do + with {:ok, options} <- Jason.decode(options_json), + {:ok, options} <- + apply(String.to_existing_atom("Elixir." <> stage.module_name), :validate_opts, [ + options + ]) do changeset = PipelineStage.changeset(stage, %{options: options}) {:ok, _stage} = Repo.update(changeset) @@ -112,7 +116,9 @@ defmodule FrenzyWeb.PipelineController do feed = conn.assigns[:feed] with {index, _} <- Integer.parse(index), - {:ok, options} <- Jason.decode(options_json) do + module_atom <- String.to_existing_atom("Elixir." <> module_name), + {:ok, options} <- Jason.decode(options_json), + {:ok, options} <- apply(module_atom, :validate_opts, [options]) do changeset = Ecto.build_assoc(feed, :pipeline_stages, %{ index: index,