Hey! I recorded two video courses!

My two video courses are called: Build an MVP with Elixir and Building Forms with Phoenix LiveView
If you like this article, you will also like the courses! Check them out here!

Today’s post is a tiny one. I recently added a configuration option to Phx2ban that allows users to define routes that should be excluded from the Web Application Firewall (WAF) rules of Phx2ban. Basically, any request to any of the given routes should be ignored.

I wanted to allow the user to define the routes as strings (/my-path/foo) but also as regex (~r/my-path/*). We use NimbleOptions to validate the Phx2ban config, but it does not define a regex type, so I was at a loss about how to allow both strings and regexes as a NimbleOption.

Luckily, I realized pretty quickly that the notation ~r/foo/ simply returns a Regex struct, although it’s kind of hidden because of the way the Regex is printed out:

iex> IO.inspect(~r/foo/)
~r/foo/

However, I also learned that you can print out all information about a data type using the i/1 helper function. This revealed that the shorthand notation ~r/foo/ is simply a Regex-struct!

iex> i ~r/foo/
Term
  ~r/foo/
Data type
  Regex
Description
  This is a struct representing a regular expression. It is commonly
  represented using the `~r` sigil syntax, that is
  defined in the `Kernel.sigil_r/2` macro.
Raw representation
  %Regex{
    re_pattern: {...},
    source: "foo",
    opts: [],
    re_version: {"8.44 2020-02-12", :little}
  }
Reference modules
  Regex, :re
Implemented protocols
  IEx.Info, Inspect

This meant that I could allow a regex in my NimbleOptions field with the type definition: {:struct, Regex}.

This was my final type definition:

definition =
  NimbleOptions.new!(
    routes_to_ignore: [
      default: [],
      type: {:list, {:or, [:string, {:struct, Regex}]}}
    ]
  )

Now, I was able to validate that the new configuration field only receives strings and regexes as input values:

iex> NimbleOptions.validate(%{routes_to_ignore: ["/wp-admin.php", ~r/django/]}, definition)
{:ok, %{routes_to_ignore: ["/wp-admin.php", ~r/django/]}}

🔗 Conclusion

And that’s it! I hope you enjoyed this article! If you want to support me, you can buy my firewall for Phoenix Phx2Ban or my book or video courses (one and two). Follow me on Bluesky or subscribe to my newsletter below if you want to get notified when I publish the next blog post. Until next time! Cheerio 👋

Stay updated about my work