defmodule ExFinancialModelingPrep.Helpers do @moduledoc """ Assist with assigning and defining type """ @doc """ Helper function to convert a map into specified defstruct when parsing through HTTP response with defined structs """ @spec resource_to_struct(map, any) :: %{:__struct__ => any, optional(any) => any} def resource_to_struct(resource, struct) do resource |> Map.to_list() |> Enum.map(fn {key, value} -> {String.to_existing_atom(Macro.underscore(key)), parse_binary(value)} end) |> Enum.into(%{}) |> Map.put(:__struct__, struct) end @doc """ DO NOT USE: Creates a struct file in root directory for ease of development and speeding up process. Should be yetted after project is completed. """ def create_struct_file(item, module_name) do File.write( "#{Macro.underscore(module_name)}.ex", "defmodule ExFinancialModelingPrep.Struct.#{module_name} do @moduledoc \"\"\" Struct: #{module_name} \"\"\" use TypedStruct typedstruct do #{define_struct(item)} \n end end", [:write] ) end @doc """ Helper to quickly define types """ @spec define_struct(map) :: list def define_struct(item) do item |> Map.to_list() |> Enum.map(fn {key, value} -> key = key |> Macro.underscore() type = cond do is_float(value) -> "float()" is_integer(value) -> "integer()" define_string(value) == :string -> "String.t()" define_string(value) == :date -> "Date.t()" define_string(value) == :date_time -> "DateTime.t()" end "\n field(:#{key}, #{type})" end) end @doc """ Parses Binary string to appropriate type ## Examples iex> ExFinancialModelingPrep.Helpers.parse_binary("2022-10-31") ~D[2022-10-31] iex> ExFinancialModelingPrep.Helpers.parse_binary("2022-10-27 18:01:14") ~U[2022-10-27 18:01:14Z] iex> ExFinancialModelingPrep.Helpers.parse_binary("AAPL") "AAPL" """ @spec parse_binary(binary) :: String.t() | DateTime.t() | Date.t() | float() | integer() def parse_binary(value) do define_string(value) |> case do :date -> to_date(value) :date_time -> to_date_time(value) :string -> value value -> value end end @doc """ Informs string type :date, :date_time, :string """ @spec define_string(binary) :: :date | :date_time | :string | float() | integer() def define_string(string) when is_binary(string) do cond do Regex.match?(~r/^(([0-9]{4})-([0-9]{2})-([0-9]{2}))$/, string) -> :date Regex.match?( ~r/^(([0-9]{4})-([0-9]{2})-([0-9]{2})) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/, string ) -> :date_time true -> :string end end def define_string(value), do: value # Converts Financial Modeling Prep date string to Date.t() @spec to_date(binary) :: Date.t() defp to_date(string) do string |> String.split("-") |> Enum.map(&String.to_integer/1) |> then(fn [year, month, day] -> Date.new!(year, month, day) end) end # Converts Financial Modeling Prep date string to DateTime.t() @spec to_date_time(binary) :: DateTime.t() defp to_date_time(string) do [date_string, time] = String.split(string, " ") [hour, min, sec] = time |> String.split(":") |> Enum.map(&String.to_integer/1) time = Time.new!(hour, min, sec) date = to_date(date_string) DateTime.new!(date, time) end end