Configure a loader

A loader returns the data for a remote schema. The remote schemas are defined in a schema like this.

...
  properties: %{
    int: {:ref, "http://localhost:1234/int.exon"}
  }
...

A loader will be configured like this.

config :xema, loader: My.Loader

A loader is a module which use the behaviour Xema.Loader.

defmodule My.Loader do
  @moduledoc false

  @behaviour Xema.Loader

  @spec fetch(binary) :: {:ok, map} | {:error, any}
  def fetch(uri) do
    with {:ok, response} <- get(uri), do: eval(response, uri)
  end

  defp get(uri) do
    case HTTPoison.get(uri) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        {:ok, body}

      {:ok, %HTTPoison.Response{status_code: 404}} ->
        {:error, "Remote schema '#{uri}' not found."}

      {:ok, %HTTPoison.Response{status_code: code}} ->
        {:error, "code: #{code}"}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp eval(str, uri) do
    {data, _} = Code.eval_string(str)
    {:ok, data}
  rescue
    error -> {:error, %{error | file: URI.to_string(uri)}}
  end
end

The function fetch/1 will be called by Xema and expects an %URI{}. The return value must be a tuple of :ok and the required data for a schema or an error tuple.

Note! This loader use Code.eval_string/1 and eval is always evil.

Warning: string can be any Elixir code and will be executed with the same privileges as the Erlang VM: this means that such code could compromise the machine (for example by executing system commands). Don’t use eval_string/3 with untrusted input (such as strings coming from the network). -- Elixir API

File loader

A loader to read schema from the local file system.

In the schema:

...
  properties: %{
    int: {:ref, "int.exon"}
  }
...

The loader:

defmodule My.Loader do
  @moduledoc false

  @behaviour Xema.Loader

  @spec fetch(binary) :: {:ok, map} | {:error, any}
  def fetch(uri) do
    "path/to/schemas"
    |> Path.join(uri.path)
    |> File.read!()
    |> eval(uri)
  end

  defp eval(str, uri) do
    {data, _} = Code.eval_string(str)
    {:ok, data}
  rescue
    error -> {:error, %{error | file: URI.to_string(uri)}}
  end
end

JSON Schema loader

If the scheme is created with Xema.from_json_schema/2, the loader must return the decoded JSON.

In this case the file loader looks like this:

defmodule My.Loader do
  @moduledoc false

  @behaviour Xema.Loader

  @spec fetch(binary) :: {:ok, map} | {:error, any}
  def fetch(uri) do
    "path/to/schemas"
    |> Path.join(uri.path)
    |> File.read!()
    |> Jason.decode()
  end
end