lib/notion_api.ex

defmodule Notion do
  @moduledoc false

  def get_documentation do
    File.ls!("#{__DIR__}/docs")
    |> format_documentation
  end

  defp format_documentation(files) do
    Enum.reduce(files, %{}, fn file, module_names ->
      json =
        File.read!("#{__DIR__}/docs/#{file}")
        |> Jason.decode!()

      doc = Notion.Documentation.new(json, file)

      module_names
      |> Map.put_new(doc.module, [])
      |> update_in([doc.module], &(&1 ++ [doc]))
    end)
  end
end

alias Notion.Documentation
require Logger

Enum.each(Notion.get_documentation(), fn {module_name, functions} ->
  module =
    module_name
    |> String.split(".")
    |> Enum.map(&Macro.camelize/1)
    |> Enum.reduce(Notion, &Module.concat(&2, &1))

  Logger.warning("module: #{inspect(module)}", "🐛": :" 355217549")

  defmodule module do
    Enum.each(functions, fn doc ->
      function_name = doc.function

      path_params_with_values = Documentation.path_params_with_values(doc)
      function_arguments = Documentation.function_arguments(doc)
      required_params_with_values = Documentation.required_params_with_values(doc)

      @doc """
      #{Documentation.to_doc_string(doc)}
      """
      def unquote(function_name)(unquote_splicing(function_arguments), optional_params \\ %{})
          when is_map(optional_params) do
        required_params_map = Enum.into(unquote(required_params_with_values), %{})

        # Logger.warning("required_params_map: #{inspect(required_params_map)}", "🐛": :" 1231437220")

        url = Application.get_env(:notion_api, :base_url, "https://api.notion.com")

        # path_params are substituted into the url in the path_parameter_substition funtion
        # the remaining required and optional params are mereged here and eventually converted to JSON in the body_params_processing function

        param_map =
          optional_params
          # strip out empty optional params.  importantly, retain empty required params
          |> Map.reject(fn {_, v} -> v == nil end)
          |> Map.merge(required_params_map)
          # token is special case we inject into header
          |> Map.reject(fn {k, _} -> k == :token end)
          |> Enum.map(fn {key, val} ->
            # Logger.warning("param key: #{inspect(key)}", "🐛": :" 801437221")
            # Logger.warning("param val: #{inspect(val)}", "🐛": :" 801437222")

            convert_param_value(key, val)
          end)
          # convert into map
          |> Enum.into(%{})

        # Logger.warning("param_map: #{inspect(param_map)}", "🐛": :" 801437223")

        # Logger.warning(
        #   "unq path_params_with_values: #{inspect(unquote(path_params_with_values))}",
        #   "🐛": :" 801437224"
        # )

        # Logger.warning(
        #   "argument_value_keyword_list: #{inspect(unquote(argument_value_keyword_list))}",
        #   "🐛": :" 801437225"
        # )

        url_path =
          path_parameter_substition(unquote(doc.endpoint), unquote(path_params_with_values))

        headers = [
          {"Content-Type", "application/json; charset=utf-8"},
          {"Authorization", "Bearer #{get_token(optional_params)}"},
          {"Notion-Version", "2022-02-22"}
        ]

        case unquote(doc.function) do
          :get ->
            get!(
              "#{url}/#{url_path}",
              headers
            )

          :patch ->
            patch!(
              "#{url}/#{url_path}",
              headers,
              body_params_processing(
                unquote(function_name),
                param_map,
                unquote(function_arguments)
              )
            )

          :post ->
            post!(
              "#{url}/#{url_path}",
              headers,
              body_params_processing(
                unquote(function_name),
                param_map,
                unquote(function_arguments)
              )
            )

          :delete ->
            Logger.error("TODO 3577386783", "🐛": :" 3577386783")

          _ ->
            Logger.error("Should never get here", "🐛": :" 3577386789")
        end
      end
    end)

    defp get!(url, headers) do
      # Logger.warning("get: #{inspect(url)}", "🐛": :" 12345280240")
      Application.get_env(:notion_api, :web_http_client, Notion.DefaultClient).get!(url, headers)
    end

    defp post!(url, headers, body) do
      # Logger.warning("post: #{inspect(url)}", "🐛": :" 12345280440")
      # Logger.warning("post: #{inspect(headers)}", "🐛": :" 12345280441")
      # Logger.warning("post: #{inspect(body)}", "🐛": :" 12345280442")

      Application.get_env(:notion_api, :web_http_client, Notion.DefaultClient).post!(
        url,
        headers,
        body
      )
    end

    defp patch!(url, headers, body) do
      # Logger.warning("patch: #{inspect(url)}", "🐛": :" 1234215315")
      # Logger.warning("patch: #{inspect(headers)}", "🐛": :" 1234215316")
      # Logger.warning("patch: #{inspect(body)}", "🐛": :" 1234215317")

      Application.get_env(:notion_api, :web_http_client, Notion.DefaultClient).patch!(
        url,
        headers,
        body
      )
    end

    defp get_token(%{token: token}), do: token
    defp get_token(_), do: Application.get_env(:notion_api, :internal_integration_token)

    # if we have any non-Jason supported types, we can convert them here
    defp convert_param_value(key, value) do
      {key, value}
    end

    defp path_parameter_substition(endpoint, path_params_with_values) do
      # Logger.warning("path_parameter_substition endpoint: #{inspect(endpoint)}",
      #   "🐛": :" 1339620391"
      # )

      # Logger.warning(
      #   "path_parameter_substition path_params_with_values: #{inspect(path_params_with_values)}",
      #   "🐛": :" 1339620392"
      # )

      Enum.reduce(path_params_with_values, endpoint, fn {key, val}, acc ->
        # Logger.warning("{key, val}: #{inspect({key, val})}", "🐛": :" 794991620")
        # Logger.warning("acc: #{inspect(acc)}", "🐛": :" 794991620")

        path_sub_key = to_string(key)
        String.replace(acc, path_sub_key, val)
      end)

      # |> tap(fn x ->
      #   Logger.warning("path_parameter_substition return value: #{inspect(x)}",
      #     "🐛": :" 3580239419"
      #   )
      # end)
    end

    # notion API seems to only use JASON for reqest body and response body
    defp body_params_processing(_, params, _) do
      Jason.encode!(params)
    end
  end
end)