defmodule Kraken do alias Kraken.Pipelines def call(events, opts \\ %{}) @spec call(map()) :: map() | {:error, any()} def call(event, opts) when is_map(event) do case pipeline_ready_for_event(event) do {:ok, pipeline} -> Pipelines.call(pipeline, event, opts) {:error, error} -> {:error, error} end end @spec call(list(map())) :: list(map() | {:error, any()}) | {:error, any()} def call(events, opts) when is_list(events) do cast_or_call_for_list(:call, events, opts) end def cast(events, opts \\ %{}) @spec cast(map(), map()) :: reference() | list(reference()) | {:error, any()} def cast(event, opts) when is_map(event) do case pipeline_ready_for_event(event) do {:ok, pipeline} -> Pipelines.cast(pipeline, event, opts) {:error, error} -> {:error, error} end end @spec cast(list(map()), map()) :: reference() | list(reference()) | {:error, any()} def cast(events, opts) when is_list(events) do cast_or_call_for_list(:cast, events, opts) end @spec stream(list(map()), map()) :: Enumerable.t() | {:error, any()} def stream(events, opts \\ %{}) when is_list(events) do streams = events |> Enum.group_by(&Map.get(&1, "type")) |> Enum.map(fn {_type, events} -> case pipeline_ready_for_event(hd(events)) do {:ok, pipeline} -> do_call_pipeline(pipeline, :stream, events, opts) {:error, error} -> Stream.map(events, fn _event -> {:error, error} end) end end) run_streams(streams, self()) build_output_stream(length(streams)) end defp pipeline_ready_for_event(event) do with {:ok, type} <- fetch_type(event), {:ok, pipeline} <- get_route(type), :ready <- Pipelines.ready?(pipeline) do {:ok, pipeline} else {:error, error} -> {:error, error} end end defp cast_or_call_for_list(action, events, opts) do events |> Enum.group_by(&Map.get(&1, "type")) |> Enum.map(fn {_type, events} -> case pipeline_ready_for_event(hd(events)) do {:ok, pipeline} -> {:task, Task.async(fn -> do_call_pipeline(pipeline, action, events, opts) end)} {:error, error} -> Enum.map(events, fn _event -> {:error, error} end) end end) |> Enum.map(fn {:task, task} -> Task.await(task, :infinity) errors -> errors end) |> List.flatten() end defp do_call_pipeline(pipeline, action, events, opts) when is_list(events) do apply(Pipelines, action, [pipeline, events, opts]) end defp run_streams(streams, pid) do Enum.each(streams, fn stream -> Task.async(fn -> Enum.each(stream, &send(pid, {:event, &1})) send(pid, :done) end) end) end defp build_output_stream(streams_count) do Stream.resource( fn -> streams_count end, fn left -> if left > 0 do receive do {:event, event} -> {[event], left} :done -> {[], left - 1} end else {:halt, 0} end end, fn _ -> :ok end ) end defp fetch_type(event) do case Map.get(event, "type") do nil -> {:error, :no_type} type -> {:ok, type} end end defp get_route(type) do with {:ok, routes} <- Kraken.Routes.all(), pipeline when is_binary(pipeline) <- Map.get(routes, type) do {:ok, pipeline} else nil -> {:error, :no_route_for_type} {:error, :no_routes} -> {:error, :no_routes} end end end