+ end
+ end
+
+ def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
+ with {:ok, object} <-
+ object
+ |> AddRemoveValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
+
+ def cast_and_apply_and_stringify_with_history(object) do
+ do_separate_with_history(object, fn object ->
+ with {:ok, object_data} <- cast_and_apply(object),
+ object_data <- object_data |> stringify_keys() do
+ {:ok, object_data}
+ end
+ end)
+ end
+
+ def cast_and_apply(%{"type" => "Question"} = object) do
+ QuestionValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Answer"} = object) do
+ AnswerValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
+ AudioVideoValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => "Event"} = object) do
+ EventValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
+ ArticleNotePageValidator.cast_and_apply(object)
+ end
+
+ def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
+
+ # is_struct/1 appears in Elixir 1.11
+ def stringify_keys(%{__struct__: _} = object) do
+ object
+ |> Map.from_struct()
+ |> stringify_keys
+ end
+
+ def stringify_keys(object) when is_map(object) do
+ object
+ |> Enum.filter(fn {_, v} -> v != nil end)
+ |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
+ end
+
+ def stringify_keys(object) when is_list(object) do
+ object
+ |> Enum.map(&stringify_keys/1)
+ end
+
+ def stringify_keys(object), do: object
+
+ def fetch_actor(object) do
+ with actor <- Containment.get_actor(object),
+ {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
+ User.get_or_fetch_by_ap_id(actor)
+ end
+ end
+
+ def fetch_actor_and_object(object) do
+ fetch_actor(object)
+ Object.normalize(object["object"], fetch: true)
+ :ok
+ end
+
+ defp for_each_history_item(
+ %{"type" => "OrderedCollection", "orderedItems" => items} = history,
+ object,
+ fun
+ ) do
+ processed_items =
+ Enum.map(items, fn item ->
+ with item <- Map.put(item, "id", object["id"]),
+ {:ok, item} <- fun.(item) do
+ item
+ else
+ _ -> nil
+ end
+ end)
+
+ if Enum.all?(processed_items, &(not is_nil(&1))) do
+ {:ok, Map.put(history, "orderedItems", processed_items)}