defmodule Supabase.ClientOptions do @moduledoc """ A simple changeset that validates and parses client options. Usually this is used internally by `Supabase` module, but can be used to validate and parse client options manually using `parse/1` function. ## Options - `:client_name` - The name of the client. This is used to identify the client in the connection pool. This option is required. - `:db` - The database options. This is used to configure the database connection. This option is required. - `:schema` - The default schema to use. Defaults to `"public"`. - `:global` - Global options. This is used to configure global options that will be used on each request. This option is required. - `:headers` - Additional headers to use on each request. - `:auth` - Authentication options. This is used to configure authentication options. This option is required. - `:auto_refresh_token` - Automatically refresh the token when it expires. Defaults to `true`. - `:debug` - Enable debug mode. Defaults to `false`. - `:detect_session_in_url` - Detect session in URL. Defaults to `true`. - `:flow_type` - Authentication flow type. Defaults to `"web"`. - `:persist_session` - Persist session. Defaults to `true`. - `:storage` - Storage type. - `:storage_key` - Storage key. """ import Ecto.Changeset alias Supabase.Types.Atom @type t :: %{ client_name: atom, db: %{ schema: String.t() }, global: %{ headers: Map.t() }, auth: %{ auto_refresh_token: boolean(), debug: boolean(), detect_session_in_url: boolean(), flow_type: String.t(), persist_session: boolean(), storage: String.t(), storage_key: String.t() } } @base_types %{ db: :map, global: :map, auth: :map, client_name: Ecto.ParameterizedType.init(Atom, []) } @db_types %{schema: :string} @global_types %{headers: :map} @auth_types %{ auto_refresh_token: :boolean, debug: :boolean, detect_session_in_url: :boolean, flow_type: :string, persist_session: :boolean, storage: :string, storage_key: :string } @spec parse(map) :: {:ok, Supabase.ClientOptions.t()} | {:error, Ecto.Changeset.t()} | {:error, {atom, Ecto.Changeset.t()}} def parse(attrs) do with {:ok, db} <- cast_db(attrs[:db] || %{}), {:ok, global} <- cast_global(attrs[:global] || %{}), {:ok, auth} <- cast_auth(attrs[:auth] || %{}) do {%{}, @base_types} |> cast(attrs, Map.keys(@base_types)) |> validate_required(~w[client_name]a) |> put_change(:db, db) |> put_change(:global, global) |> put_change(:auth, auth) |> apply_action(:parse) end end @spec cast_db(map) :: {:ok, map} | {:error, {:db, Ecto.Changeset.t()}} defp cast_db(attrs) do {%{}, @db_types} |> cast(attrs, Map.keys(@db_types)) |> maybe_put_default_schema() |> apply_action(:parse_db) |> case do {:ok, data} -> {:ok, data} {:error, changeset} -> {:error, {:db, changeset}} end end defp maybe_put_default_schema(changeset) do if get_change(changeset, :schema) do changeset else put_change(changeset, :schema, "public") end end @spec cast_global(map) :: {:ok, map} | {:error, {:global, Ecto.Changeset.t()}} defp cast_global(attrs) do {%{}, @global_types} |> cast(attrs, Map.keys(@global_types)) |> apply_action(:parse_global) |> case do {:ok, data} -> {:ok, data} {:error, changeset} -> {:error, {:global, changeset}} end end @spec cast_auth(map) :: {:ok, map} | {:error, {:auth, Ecto.Changeset.t()}} defp cast_auth(attrs) do {%{}, @auth_types} |> cast(attrs, Map.keys(@auth_types)) |> maybe_put_default_flow_type() |> maybe_persist_session() |> maybe_debug() |> maybe_auto_refresh_token() |> maybe_detect_session_in_url() |> apply_action(:parse_auth) |> case do {:ok, data} -> {:ok, data} {:error, changeset} -> {:error, {:auth, changeset}} end end defp maybe_put_default_flow_type(changeset) do if get_change(changeset, :flow_type) do changeset else put_change(changeset, :flow_type, "magicLink") end end defp maybe_persist_session(changeset) do if get_change(changeset, :persist_session) do changeset else put_change(changeset, :persist_session, true) end end defp maybe_debug(changeset) do if get_change(changeset, :debug) do changeset else put_change(changeset, :debug, false) end end defp maybe_auto_refresh_token(changeset) do if get_change(changeset, :auto_refresh_token) do changeset else put_change(changeset, :auto_refresh_token, true) end end defp maybe_detect_session_in_url(changeset) do if get_change(changeset, :detect_session_in_url) do changeset else put_change(changeset, :detect_session_in_url, true) end end @spec to_client_info(t, list(Supabase.Connection.t())) :: Supabase.Client.params() def to_client_info(data, conns) do connections = Enum.map(conns, &Map.new([{&1.alias, &1.name}])) client_info = data |> Map.take(~w[db global auth]a) |> Map.put(:connections, connections) |> Map.put(:name, data[:client_name]) [ name: data[:client_name], client_info: client_info ] end end