ycherniavskyi

ycherniavskyi

Collect all function return tuples in the module with macro and provide access to them with a helper function

Let’s say I have next controller where {:error, _, _} action result processed by FallbackController:

defmodule SomeProjectWeb.Api.V1.SessionController do
  use SomeProjectWeb, :controller

  alias SomeProject.Session
  alias SomeProject.Session.Otp
  alias SomeProjectWeb.Api.V1.CastAndValidateRenderError
  alias SomeProjectWeb.Api.V1.FallbackController
  alias SomeProjectWeb.Api.V1.{CommonSchema, SessionSchema}

  plug(OpenApiSpex.Plug.CastAndValidate, render_error: CastAndValidateRenderError)

  action_fallback(FallbackController)

  tags(["session"])

  def action(conn, _) do
    apply(__MODULE__, action_name(conn), [conn, conn.params, conn.body_params])
  end

  operation(:otp_create,
    summary: "otp_create summary",
    description: "otp_create description",
    requestBody: {
      "request body description",
      "application/json",
      SessionSchema.OtpCreateRequest,
      required: true
    },
    responses: [
      ok: {
        "ok description",
        "application/json",
        SessionSchema.OtpCreateResponse
      },
      unauthorized: "...",
      unprocessable_entity: {
        "Unprocessable Content, ...",
        "application/json",
        %Schema{
          allOf: [
            CommonSchema.ErrorResponse,
            %Schema{
              type: :object,
              properties: %{
                code: %Schema{
                  enum: [
                    :no_otp_delivery_channel,
                    :runout_signup_account
                  ]
                }
              }
            }
          ]
        }
      },
      internal_server_error: {
        "Internal Server Error, ...",
        "application/json",
        %Schema{
          allOf: [
            CommonSchema.ErrorResponse,
            %Schema{
              type: :object,
              properties: %{
                code: %Schema{
                  enum: [
                    :external_api,
                    :demo_account_manager
                  ]
                }
              }
            }
          ]
        }
      }
    ]
  )

  def otp_create(conn, _params, %{user_email: email} = _body_params) do
    case SomeProject.DemoAccountManager.retrieve(email) do
      {:ok, i_account} ->
        case SomeProject.ExternalApi.create_otp(i_account) do
          {200, %{"success" => 1, "type" => "email", "from" => email}} ->
            otp = Session.create_otp(%{i_account: i_account, demo: true})

            render(conn, otp_id: otp.id, type: "email", from: email)

          {500, %{"faultcode" => "channel-error"}} ->
            {:error, :unprocessable_entity, :no_otp_delivery_channel}

          _ ->
            {:error, :internal_server_error, :external_api}
        end

      {:error, :runout} ->
        {:error, :unprocessable_entity, :runout_signup_account}

      {:error, _} ->
        {:error, :internal_server_error, :demo_account_manager}
    end
  end
end

For now, I manually sync possible error codes for each result with relative result schema. That is diffidently not convenient and will lead to unsync in the future.

I want to implement some type of module macro that will collect and group all its functions :error results and introduce the helper function to access them.
So I could replace:

      internal_server_error: {
        "Internal Server Error, ...",
        "application/json",
        %Schema{
          allOf: [
            CommonSchema.ErrorResponse,
            %Schema{
              type: :object,
              properties: %{
                code: %Schema{
                  enum: [
                    :external_api,
                    :demo_account_manager
                  ]
                }
              }
            }
          ]
        }
      }

with:

      internal_server_error: {
        "Internal Server Error, ...",
        "application/json",
        %Schema{
          allOf: [
            CommonSchema.ErrorResponse,
            %Schema{
              type: :object,
              properties: %{
                code: %Schema{
                  enum: error_codes(:otp_create, :internal_server_error)
                }
              }
            }
          ]
        }
      }

(for sure such the final helper function could produce a more significant piece of repetitive code, but here I want to highlight its core functionality)

What is the best way to implement such functionality?

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

We're in Beta

About us Mission Statement