Skip to main content

lib/terminus_db/prefix.ex

defmodule TerminusDB.Prefix do
  @moduledoc """
  Prefix management API for TerminusDB.

  Wraps the `/api/prefix/{path}` and `/api/prefixes/{path}` endpoints for
  managing custom prefix mappings in a database.

  All functions require a `TerminusDB.Config` scoped to a database.

  ## Quick start

      config =
        TerminusDB.Config.new(endpoint: "http://localhost:6363")
        |> TerminusDB.Config.with_database("mydb")

      {:ok, uri} = TerminusDB.Prefix.get(config, "ex")
      {:ok, _} = TerminusDB.Prefix.add(config, "ex", "http://example.org/")
      {:ok, _} = TerminusDB.Prefix.update(config, "ex", "http://example.com/")
      {:ok, _} = TerminusDB.Prefix.delete(config, "ex")

  """

  alias TerminusDB.{Client, Config, Error}

  @type prefix_opt :: {:organization, String.t()} | {:repo, String.t()}

  defp prefix_base_path(config, opts) do
    org = opts[:organization] || config.organization

    case config.database do
      nil ->
        {:error, %Error{reason: :config, message: "no database scoped in config"}}

      db ->
        repo = opts[:repo] || config.repo
        branch = config.branch
        {:ok, "prefix/#{org}/#{db}/#{repo}/branch/#{branch}"}
    end
  end

  defp prefixes_path(config, opts) do
    org = opts[:organization] || config.organization

    case config.database do
      nil -> {:error, %Error{reason: :config, message: "no database scoped in config"}}
      db -> {:ok, "prefixes/#{org}/#{db}"}
    end
  end

  @doc """
  Gets a single prefix IRI by name.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:prefix_uri" => "http://example.org/"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, uri} = TerminusDB.Prefix.get(config, "ex")
      iex> uri
      "http://example.org/"

  """
  @spec get(Config.t(), String.t(), [prefix_opt()]) :: {:ok, String.t()} | {:error, Error.t()}
  def get(config, prefix_name, opts \\ []) do
    with {:ok, base} <- prefix_base_path(config, opts) do
      path = "#{base}/#{prefix_name}"

      case Client.request(config, :get, path, area: :prefix) do
        {:ok, %{"api:prefix_uri" => uri}} -> {:ok, uri}
        {:ok, body} -> {:ok, body}
        {:error, _} = error -> error
      end
    end
  end

  @doc """
  Gets a single prefix IRI by name, or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:prefix_uri" => "http://example.org/"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.get!(config, "ex")
      "http://example.org/"

  """
  @spec get!(Config.t(), String.t(), [prefix_opt()]) :: String.t()
  def get!(config, prefix_name, opts \\ []) do
    case get(config, prefix_name, opts) do
      {:ok, uri} -> uri
      {:error, error} -> raise error
    end
  end

  @doc """
  Adds a new prefix mapping.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, resp} = TerminusDB.Prefix.add(config, "ex", "http://example.org/")
      iex> resp["api:status"]
      "api:success"

  """
  @spec add(Config.t(), String.t(), String.t(), [prefix_opt()]) ::
          {:ok, map()} | {:error, Error.t()}
  def add(config, prefix_name, uri, opts \\ []) do
    with {:ok, base} <- prefix_base_path(config, opts) do
      path = "#{base}/#{prefix_name}"
      Client.request(config, :post, path, json: %{"uri" => uri}, area: :prefix)
    end
  end

  @doc """
  Adds a new prefix mapping, or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.add!(config, "ex", "http://example.org/")
      %{"api:status" => "api:success"}

  """
  @spec add!(Config.t(), String.t(), String.t(), [prefix_opt()]) :: map()
  def add!(config, prefix_name, uri, opts \\ []) do
    case add(config, prefix_name, uri, opts) do
      {:ok, body} -> body
      {:error, error} -> raise error
    end
  end

  @doc """
  Updates an existing prefix mapping.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, resp} = TerminusDB.Prefix.update(config, "ex", "http://example.com/")
      iex> resp["api:status"]
      "api:success"

  """
  @spec update(Config.t(), String.t(), String.t(), [prefix_opt()]) ::
          {:ok, map()} | {:error, Error.t()}
  def update(config, prefix_name, uri, opts \\ []) do
    with {:ok, base} <- prefix_base_path(config, opts) do
      path = "#{base}/#{prefix_name}"
      Client.request(config, :put, path, json: %{"uri" => uri}, area: :prefix)
    end
  end

  @doc """
  Updates an existing prefix mapping, or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.update!(config, "ex", "http://example.com/")
      %{"api:status" => "api:success"}

  """
  @spec update!(Config.t(), String.t(), String.t(), [prefix_opt()]) :: map()
  def update!(config, prefix_name, uri, opts \\ []) do
    case update(config, prefix_name, uri, opts) do
      {:ok, body} -> body
      {:error, error} -> raise error
    end
  end

  @doc """
  Creates or updates a prefix mapping (upsert).

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, resp} = TerminusDB.Prefix.upsert(config, "ex", "http://example.org/")
      iex> resp["api:status"]
      "api:success"

  """
  @spec upsert(Config.t(), String.t(), String.t(), [prefix_opt()]) ::
          {:ok, map()} | {:error, Error.t()}
  def upsert(config, prefix_name, uri, opts \\ []) do
    with {:ok, base} <- prefix_base_path(config, opts) do
      path = "#{base}/#{prefix_name}"

      Client.request(config, :put, path,
        json: %{"uri" => uri},
        params: [create: true],
        area: :prefix
      )
    end
  end

  @doc """
  Creates or updates a prefix mapping (upsert), or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.upsert!(config, "ex", "http://example.org/")
      %{"api:status" => "api:success"}

  """
  @spec upsert!(Config.t(), String.t(), String.t(), [prefix_opt()]) :: map()
  def upsert!(config, prefix_name, uri, opts \\ []) do
    case upsert(config, prefix_name, uri, opts) do
      {:ok, body} -> body
      {:error, error} -> raise error
    end
  end

  @doc """
  Deletes a prefix mapping.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, resp} = TerminusDB.Prefix.delete(config, "ex")
      iex> resp["api:status"]
      "api:success"

  """
  @spec delete(Config.t(), String.t(), [prefix_opt()]) ::
          {:ok, map()} | {:error, Error.t()}
  def delete(config, prefix_name, opts \\ []) do
    with {:ok, base} <- prefix_base_path(config, opts) do
      path = "#{base}/#{prefix_name}"
      Client.request(config, :delete, path, area: :prefix)
    end
  end

  @doc """
  Deletes a prefix mapping, or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"api:status" => "api:success"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.delete!(config, "ex")
      %{"api:status" => "api:success"}

  """
  @spec delete!(Config.t(), String.t(), [prefix_opt()]) :: map()
  def delete!(config, prefix_name, opts \\ []) do
    case delete(config, prefix_name, opts) do
      {:ok, body} -> body
      {:error, error} -> raise error
    end
  end

  @doc """
  Gets all prefix mappings for the database.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@base" => "terminusdb:///data/", "@schema" => "terminusdb:///schema#"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> {:ok, prefixes} = TerminusDB.Prefix.all(config)
      iex> prefixes["@schema"]
      "terminusdb:///schema#"

  """
  @spec all(Config.t(), [prefix_opt()]) :: {:ok, map()} | {:error, Error.t()}
  def all(config, opts \\ []) do
    with {:ok, path} <- prefixes_path(config, opts) do
      Client.request(config, :get, path, area: :prefix)
    end
  end

  @doc """
  Gets all prefix mappings, or raises.

  ## Examples

      iex> config = TerminusDB.Config.new(
      ...>   endpoint: "http://localhost:6363",
      ...>   adapter: fn req -> {req, Req.Response.new(status: 200, body: %{"@base" => "terminusdb:///data/", "@schema" => "terminusdb:///schema#"})} end
      ...> ) |> TerminusDB.Config.with_database("mydb")
      iex> TerminusDB.Prefix.all!(config)["@schema"]
      "terminusdb:///schema#"

  """
  @spec all!(Config.t(), [prefix_opt()]) :: map()
  def all!(config, opts \\ []) do
    case all(config, opts) do
      {:ok, body} -> body
      {:error, error} -> raise error
    end
  end
end