lib/ex_gram/macros.ex

defmodule ExGram.Macros do
  @moduledoc """
  `model/2` and `method/4` macros to build the API
  """

  import __MODULE__.Helpers

  defmacro model(name, params, description) do
    tps = struct_type_specs(params)

    initials =
      tps
      |> Enum.map(fn {id, _t} -> {id, nil} end)

    dca = params_to_decode_as(params)

    quote do
      defmodule unquote(name) do
        @moduledoc """
        #{unquote(description)}

        Check the documentation of this model in https://core.telegram.org/bots/api##{unquote(name) |> inspect() |> String.split(".") |> Enum.at(-1) |> String.downcase()}
        """
        defstruct unquote(initials)
        @type t :: %unquote(name){unquote_splicing(tps)}

        def decode_as() do
          %unquote(name){unquote_splicing(dca)}
        end
      end
    end
  end

  defmacro method(verb, name, params, returned, description) do
    fname =
      Macro.underscore(name)
      |> String.to_atom()

    # fnametest =
    #   "#{Macro.underscore(name)}_test"
    #   |> String.to_atom

    fname_exception =
      "#{Macro.underscore(name)}!"
      |> String.to_atom()

    analyzed = params |> Enum.map(&analyze_param/1)

    types_mand_value = mandatory_value_type(analyzed)
    types_mand_spec = mandatory_type_specs(analyzed)

    types_opt_spec = optional_type_specs(analyzed)

    {mandatory_parameters, mandatory_body} = mandatory_parameters(analyzed)

    {opt_par, opt_par_types} = optional_parameters(analyzed)

    returned_type_spec = type_to_spec(returned)

    file_parameters = file_parameters(analyzed)

    quote location: :keep do
      # Safe method
      @doc """
      #{unquote(description)}

      Check the documentation of this method in https://core.telegram.org/bots/api##{String.downcase(unquote(name))}
      """
      @spec unquote(fname)(unquote_splicing(types_mand_spec), options :: unquote(types_opt_spec)) ::
              {:ok, unquote(returned_type_spec)}
              | {:error, ExGram.Error.t()}
      def unquote(fname)(unquote_splicing(mandatory_parameters), options \\ []) do
        name = unquote(name)
        verb = unquote(verb)
        mandatory_body = unquote(mandatory_body)
        file_parameters = unquote(file_parameters)
        returned = unquote(returned)
        method_ops = Keyword.take(options, unquote(opt_par))
        mandatory_types = unquote(types_mand_value)
        optional_types = unquote(opt_par_types)

        ExGram.Macros.Executer.execute_method(
          name,
          verb,
          mandatory_body,
          file_parameters,
          returned,
          options,
          method_ops,
          mandatory_types,
          optional_types
        )
      end

      # Unsafe method
      @doc """
      Unsafe version of #{unquote(fname)}. It will return the response or raise in case of error.

      #{unquote(description)}

      Check the documentation of this method in https://core.telegram.org/bots/api##{String.downcase(unquote(name))}
      """
      @spec unquote(fname_exception)(
              unquote_splicing(types_mand_spec),
              ops :: unquote(types_opt_spec)
            ) :: unquote(returned_type_spec)
      def unquote(fname_exception)(unquote_splicing(mandatory_parameters), ops \\ []) do
        case unquote(fname)(unquote_splicing(mandatory_parameters), ops) do
          {:ok, result} -> result
          {:error, error} -> raise error
        end
      end
    end
  end
end