lib/request.ex

defmodule ExOpenAi.Request do
  @moduledoc """
  The base module for all requests in the ExOpenAi API client.

  Can be used to prepare, validate and update requests.

  ## Examples
      iex> params = %{
      ...>   model: "text-model",
      ...>   messages: [
      ...>     %{content: "hello"}
      ...>   ]
      ...> }
      iex> Request.do_change(params, promp: "this is a prompt")
      %{model: "text-model", messages: [%{content: "hello"}], prompt: "this is a prompt"}
  """

  @doc false
  defmacro __using__(_options) do
    quote do
      @doc """
      Builds the map by adding to the existing map and updates the map with the attributes passed in.

      ## Examples

          iex> params = %{
          ...>   model: "text-model",
          ...>   messages: [
          ...>     %{content: "hello"}
          ...>   ]
          ...> }
          iex> Request.do_change(params, promp: "this is a prompt")
          %{model: "text-model", messages: [%{content: "hello"}], prompt: "this is a prompt"}
      """
      @spec do_change(map, attributes :: list) :: map
      def do_change(map, attributes) do
        incoming_map =
          attributes
          |> Enum.into(%{})

        Map.merge(map, incoming_map)
      end

      @doc """
      Ensures that the map has the required fields.

      ## Examples

            iex> params = %{model: "text-model", messages: [%{content: "hello"}]}
            iex> Request.has_required_fields?(params, [:model, :messages])
            true

            iex> params = %{model: "text-model"}
            iex> Request.has_required_fields?(params, [:model, :messages])
            false
      """
      @spec has_required_fields?(map, fields :: list) :: boolean
      def has_required_fields?(map, fields) do
        keys =
          map
          |> Map.keys()

        Enum.all?(fields, fn field -> field in keys end)
      end

      @doc """
      Prepares the map by removing any fields that are not in the list of fields. and ensures required fields are present.

      ## Examples

            iex> params = %{
            ...>   model: "text-model",
            ...>   messages: [
            ...>     %{content: "hello"}
            ...>   ]
            ...> }
            iex> Request.prepare(params, [], [:model, :messages])
            %{model: "text-model", messages: [%{content: "hello"}]}

            iex> params = %{
            ...>   model: "text-model",
            ...>   messages: [
            ...>     %{content: "hello"}
            ...>   ]
            ...> }
            iex> Request.prepare(params, [], [:model, :messages, :prompt])
            ** (RuntimeError) Required fields are missing
      """
      @spec prepare(map, fields :: list, required_fields :: list) :: map
      def prepare(map, fields, required_fields) do
        if has_required_fields?(map, required_fields) do
          map
          |> delete_invalid_fields(fields ++ required_fields)
        else
          raise "Required fields are missing"
        end
      end

      @doc """
      Deletes any fields that are not in the list of fields.

      ## Examples

            iex> params = %{
            ...>   model: "text-model",
            ...>   messages: [
            ...>     %{content: "hello"}
            ...>   ]
            ...> }
            iex> Request.delete_invalid_fields(params, [:model, :messages])
            %{model: "text-model", messages: [%{content: "hello"}]}
      """
      @spec delete_invalid_fields(map, fields :: list) :: map
      def delete_invalid_fields(map, fields) do
        map
        |> Enum.filter(fn {key, _value} -> key in fields end)
        |> Enum.into(%{})
      end
    end
  end
end