defmodule ExOpenAI.Config do @moduledoc """ Reads configuration on application start, parses all environment variables (if any) and caches the final config in memory to avoid parsing on each read afterwards. """ use GenServer @openai_url "https://api.openai.com/v1" @config_keys [ :api_key, :organization_key, :http_options, :http_headers, :http_client, :base_url ] def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__) @impl true def init(_opts) do config = @config_keys |> Enum.map(fn key -> {key, get_config_value(key)} end) |> Map.new() {:ok, config} end # API Key def api_key, do: get_config_value(:api_key) def org_key, do: get_config_value(:organization_key) def api_url(override \\ nil) do override || get_config_value(:base_url, @openai_url) end # HTTP Options def http_options, do: get_config_value(:http_options, []) def http_headers, do: get_config_value(:http_headers, []) # HTTP client can be customized to facilitate testing def http_client, do: get_config_value(:http_client, ExOpenAI.Client) defp get_config_value(key, default \\ nil) do value = :ex_openai |> Application.get_env(key) |> parse_config_value() if is_nil(value), do: default, else: value end defp parse_config_value({:system, env_name}), do: fetch_env!(env_name) defp parse_config_value({:system, :integer, env_name}) do env_name |> fetch_env!() |> String.to_integer() end defp parse_config_value(value), do: value # System.fetch_env!/1 support for older versions of Elixir defp fetch_env!(env_name) do case System.get_env(env_name) do nil -> raise ArgumentError, message: "could not fetch environment variable \"#{env_name}\" because it is not set" value -> value end end def get(key), do: GenServer.call(__MODULE__, {:get, key}) @impl true def handle_call({:get, key}, _from, state) do {:reply, Map.get(state, key), state} end @impl true def handle_call({:put, key, value}, _from, state) do {:reply, value, Map.put(state, key, value)} end end