defmodule SyntropyWeb.Api.TaskRequest do @moduledoc false @type parsed_request :: %{prompt: String.t(), opts: keyword()} @strategies ~w(single parallel all auto) @spec parse(map()) :: {:ok, parsed_request()} | {:error, [map()]} def parse(params) do with {:ok, prompt} <- parse_prompt(Map.get(params, "prompt")), {:ok, strategy} <- parse_strategy(Map.get(params, "strategy")), {:ok, agent_ids} <- parse_agent_ids(Map.get(params, "agent_ids")), {:ok, agent_count} <- parse_positive_integer(Map.get(params, "agent_count"), 3, "agent_count"), {:ok, synthesize} <- parse_boolean(Map.get(params, "synthesize"), true, "synthesize") do {:ok, %{ prompt: prompt, opts: [] |> Keyword.put(:strategy, strategy) |> maybe_put(:agent_ids, agent_ids) |> Keyword.put(:agent_count, agent_count) |> Keyword.put(:synthesize, synthesize) }} else {:error, errors} -> {:error, errors} end end defp maybe_put(opts, _key, nil), do: opts defp maybe_put(opts, key, value), do: Keyword.put(opts, key, value) defp parse_prompt(prompt) when is_binary(prompt) do trimmed = String.trim(prompt) if trimmed == "" do {:error, [error("prompt", "must be a non-empty string")]} else {:ok, trimmed} end end defp parse_prompt(_prompt), do: {:error, [error("prompt", "is required")]} defp parse_strategy(nil), do: {:ok, :parallel} defp parse_strategy(strategy) when is_binary(strategy) do if strategy in @strategies do {:ok, String.to_atom(strategy)} else {:error, [error("strategy", "must be one of single, parallel, all, auto")]} end end defp parse_strategy(_strategy), do: {:error, [error("strategy", "must be one of single, parallel, all, auto")]} defp parse_agent_ids(nil), do: {:ok, nil} defp parse_agent_ids(agent_ids) when is_list(agent_ids) do if Enum.all?(agent_ids, &is_binary/1) do {:ok, agent_ids} else {:error, [error("agent_ids", "must be an array of strings")]} end end defp parse_agent_ids(_agent_ids), do: {:error, [error("agent_ids", "must be an array of strings")]} defp parse_positive_integer(nil, default, _field), do: {:ok, default} defp parse_positive_integer(value, _default, _field) when is_integer(value) and value > 0, do: {:ok, value} defp parse_positive_integer(value, _default, field) when is_binary(value) do case Integer.parse(value) do {parsed, ""} when parsed > 0 -> {:ok, parsed} _other -> {:error, [error(field, "must be a positive integer")]} end end defp parse_positive_integer(_value, _default, field), do: {:error, [error(field, "must be a positive integer")]} defp parse_boolean(nil, default, _field), do: {:ok, default} defp parse_boolean(value, _default, _field) when is_boolean(value), do: {:ok, value} defp parse_boolean(_value, _default, field), do: {:error, [error(field, "must be a boolean")]} defp error(field, message), do: %{field: field, message: message} end