Skip to main content

lib/demografix.ex

defmodule Demografix do
  @moduledoc """
  Official Elixir client for the Demografix APIs: genderize, agify, and nationalize.

  One client covers all three services. Each call returns the prediction fields
  plus a `Demografix.Quota` read from the response rate-limit headers. Batch calls
  return a `Demografix.Batch` of per-name predictions in input order plus one
  quota for the whole response.

  ## Construction

      client = Demografix.new("YOUR_API_KEY")
      client = Demografix.new("YOUR_API_KEY", timeout: 5_000)

  An `api_key` is required. A `nil` or blank key raises `ArgumentError`.

  ## Results and errors

  Each function returns `{:ok, result}` or `{:error, %Demografix.Error{}}`. The
  bang variants (`genderize!/3` and so on) return the result or raise the error.

      {:ok, result} = Demografix.genderize(client, "peter")
      result.gender          # => "male"
      result.quota.remaining # => 24987

  ## Aggregate over a list

  The services are built for summarizing a group of names, not labeling one person.

      {:ok, batch} = Demografix.genderize_batch(client, ["peter", "lois", "kim"])

      batch.results
      |> Enum.frequencies_by(& &1.gender)
      # => %{"male" => 1, "female" => 2}
  """

  alias Demografix.{Agify, Batch, Client, Error, Genderize, Nationalize}

  @type t :: Client.t()

  @doc """
  Build a client. `api_key` is required; a `nil` or blank key raises
  `ArgumentError` before any request. The same key works across all three
  services.

  ## Options

    * `:timeout` — receive timeout in milliseconds. Defaults to `10_000`.
  """
  @spec new(String.t(), keyword()) :: t()
  defdelegate new(api_key, opts \\ []), to: Client

  @doc """
  Predict the gender of one name.

  ## Options

    * `:country_id` — ISO 3166-1 alpha-2 code to scope the prediction. Echoed
      back uppercase on the result.
  """
  @spec genderize(t(), String.t(), keyword()) :: {:ok, Genderize.t()} | {:error, Error.t()}
  defdelegate genderize(client, name, opts \\ []), to: Client

  @doc """
  Predict the gender of up to 10 names.

  More than 10 names raises a client-side `Demografix.Error` of kind `:validation`
  before any request. Accepts the same `:country_id` option as `genderize/3`.
  """
  @spec genderize_batch(t(), [String.t()], keyword()) :: {:ok, Batch.t()} | {:error, Error.t()}
  defdelegate genderize_batch(client, names, opts \\ []), to: Client

  @doc """
  Predict the age of one name. Accepts the same `:country_id` option as
  `genderize/3`.
  """
  @spec agify(t(), String.t(), keyword()) :: {:ok, Agify.t()} | {:error, Error.t()}
  defdelegate agify(client, name, opts \\ []), to: Client

  @doc """
  Predict the age of up to 10 names. More than 10 names raises a client-side
  validation error before any request.
  """
  @spec agify_batch(t(), [String.t()], keyword()) :: {:ok, Batch.t()} | {:error, Error.t()}
  defdelegate agify_batch(client, names, opts \\ []), to: Client

  @doc """
  Predict the nationality of one name. Nationalize takes no `country_id`.
  """
  @spec nationalize(t(), String.t(), keyword()) :: {:ok, Nationalize.t()} | {:error, Error.t()}
  defdelegate nationalize(client, name, opts \\ []), to: Client

  @doc """
  Predict the nationality of up to 10 names. More than 10 names raises a
  client-side validation error before any request.
  """
  @spec nationalize_batch(t(), [String.t()], keyword()) :: {:ok, Batch.t()} | {:error, Error.t()}
  defdelegate nationalize_batch(client, names, opts \\ []), to: Client

  @doc "Like `genderize/3`, but returns the result or raises `Demografix.Error`."
  @spec genderize!(t(), String.t(), keyword()) :: Genderize.t()
  def genderize!(client, name, opts \\ []), do: unwrap(genderize(client, name, opts))

  @doc "Like `genderize_batch/3`, but returns the result or raises `Demografix.Error`."
  @spec genderize_batch!(t(), [String.t()], keyword()) :: Batch.t()
  def genderize_batch!(client, names, opts \\ []), do: unwrap(genderize_batch(client, names, opts))

  @doc "Like `agify/3`, but returns the result or raises `Demografix.Error`."
  @spec agify!(t(), String.t(), keyword()) :: Agify.t()
  def agify!(client, name, opts \\ []), do: unwrap(agify(client, name, opts))

  @doc "Like `agify_batch/3`, but returns the result or raises `Demografix.Error`."
  @spec agify_batch!(t(), [String.t()], keyword()) :: Batch.t()
  def agify_batch!(client, names, opts \\ []), do: unwrap(agify_batch(client, names, opts))

  @doc "Like `nationalize/3`, but returns the result or raises `Demografix.Error`."
  @spec nationalize!(t(), String.t(), keyword()) :: Nationalize.t()
  def nationalize!(client, name, opts \\ []), do: unwrap(nationalize(client, name, opts))

  @doc "Like `nationalize_batch/3`, but returns the result or raises `Demografix.Error`."
  @spec nationalize_batch!(t(), [String.t()], keyword()) :: Batch.t()
  def nationalize_batch!(client, names, opts \\ []),
    do: unwrap(nationalize_batch(client, names, opts))

  defp unwrap({:ok, result}), do: result
  defp unwrap({:error, %Error{} = error}), do: raise(error)
end