lib/word/definitions.ex

defmodule Wordnik.Word.Definitions do
  @moduledoc """
  word definition(s) across various dictionaries

  [Wordnik Docs](https://developer.wordnik.com/docs#!/word/getDefinitions)
  """

  alias Wordnik.Formatter.Query
  alias Wordnik.Formatter.Validator

  @typedoc """
  map of optional parameters that can be passed to `get_definitions/2` query
  """
  @type definitions_params ::
          %{
            optional(:use_canonical) => boolean,
            optional(:include_related) => boolean,
            optional(:include_tags) => boolean,
            optional(:limit) => integer,
            optional(:part_of_speech) => Formatter.Enums.part_of_speech(),
            optional(:source_dictionaries) => Formatter.Enums.source_dictionaries()
          }

  @typedoc """
  parsed JSON response to `get_definitions/2` query
  """
  @type definitions ::
          list(%{
            attributionText: String.t(),
            attributionUrl: String.t(),
            citations: [
              any
            ],
            exampleUses: [
              any
            ],
            extendedText: String.t(),
            labels: [
              any
            ],
            notes: [
              any
            ],
            partOfSpeech: String.t(),
            relatedWords: [
              any
            ],
            score: integer,
            seqString: String.t(),
            sequence: String.t(),
            sourceDictionary: String.t(),
            text: String.t(),
            textProns: [
              any
            ],
            word: String.t()
          })

  @valid_params [
    "limit",
    "part_of_speech",
    "source_dictionaries",
    "include_related",
    "use_canonical",
    "include_tags"
  ]

  defp format_url(word) do
    "http://api.wordnik.com/v4/word.json/#{word}/definitions"
  end

  defp has_valid_source_dictionaries?(%{source_dictionaries: source_dictionaries}) do
    Validator.has_valid_sources?(source_dictionaries)
  end

  defp has_valid_source_dictionaries?(%{"source_dictionaries" => source_dictionaries}) do
    Validator.has_valid_sources?(source_dictionaries)
  end

  defp has_valid_source_dictionaries?(_params), do: true

  @doc """
  get definition(s) for requested word

  ### Parameters
  - use_canonical: boolean
  - include_related: boolean
  - include_tags: boolean
  - limit: integer
  - part_of_speech: string
  - source_dictionaries: string

  ### Example
  ```elixir
  iex> get_definitions("verbose", %{part_of_speech: "noun", limit: 5})
  ```

  ### Response
  `t:Wordnik.Word.Definitions.definitions/0`

  ### Wordnik Docs
  https://developer.wordnik.com/docs#!/word/getDefinitions
  """
  @spec get_definitions(String.t(), definitions_params()) ::
          {:error, String.t()} | {:ok, definitions}
  def get_definitions(word, params \\ %{}) do
    if !has_valid_source_dictionaries?(params) do
      {:error,
       "Invalid source dictionaries provided. Provide 'all', a single dictionary, or a comma-separated list of dictionaries excluding 'all'."}
    else
      {fn_name, _} = __ENV__.function

      word
      |> format_url
      |> Query.validate_and_fetch_query(params, @valid_params, fn_name)
    end
  end
end