Writing an Elixir Plug Library

Plug is an Elixir specification for composable modules between web application. That’s a very nice way to describe middlewares. For those of you that come from the Ruby world it pretty much takes the role of Rack middlewares.
A few weeks ago I searched Google for a Plug library to validate path and query parameters declaratively on the router. I got a single result but it didn’t have any documentation and from going over the code it didn’t provide what I was looking for.
In my vision I would write my app routes as:
This would validate that the path param id
is a valid integer and that the query param active
is a valid boolean string value (either “true” or “false”).
I also wanted to be able to run a callback function of my own when some or all validations fail. This callback might, for example, return a 422 status code with an appropriate message.
I had the need, I had the vision. I decided to write my own Plug library.
Starting a New Project
To start a fresh elixir project we type$> mix new plug_validator --module Plug.Validator
The default module name would have been PlugValidator
. I named it explicitly via the --module
flag to follow the pattern of Plug.Router
and friends.
To complete the initial files structure I created some directories and moved around some files:
$> mkdir lib/plug
$> mv lib/plug_validator.ex lib/plug/validator.ex
$> mkdir test/plug
$> mv test/plug_validator.ex test/plug/validator.ex
Just to make sure everything is intact we can run$> mix test
And see that the tests passes and we haven’t broken anything.
Testing Before Implementing
When I start with a clear vision of what I would like to have, I tend to go TDD.
TDD is a shortcut for Test Driven Development and promotes the following workflow:
- Write tests for the module/code you’re about to create
- Run the tests and see them fail
- Write the minimal code to pass the tests
- Run the tests again and see them pass
- Go back to 1
Following this exact procedure might become tedious so I allow myself not to strictly follow these five steps and make shortcuts from time to time. The important thing for me is to write the tests before I write the implementation code itself. Later on I’ll explain why I feel it makes my development process and end result a lot better.
The first thing I did was to create a dummy router and some validation functions to work with. Two important decisions I made at this stage:
Plug.Validator
should be plugged betweenplug :match
andplug :dispatch
. This is to ensure we only run the validation function after the route has been matched.- A validation function should return
{:error, the_error}
in case of failed validation. Any other value returned indicates the validation has passed. This is to follow a common Elixir convention in return values. - The validation declaration will be made in the
private
storage ofPlug.Conn
. As the documentation for theprivate
storage states:
This storage is meant to be used by libraries and frameworks to avoid writing to the user storage
Exactly what I needed.
I thought the appropriate place to put the dummy router and the validation files in is test/support/
since they do not contain any tests per-se.
The dummy router shows exactly how plug validator should be used (line 8). I placed plug Plug.Validator
between the match and dispatch plugs . I also provided an error callback that receives the conn
and the error list in case of a failed validation.
The route definition itself (line 12) uses the private
storage of conn
to hold the required validations.
The next step was to implement the methods imported from Plug.Support.Validators
.
I created two very simple validations for integers and boolean values. Pay attention to the return value structure of {:error, the_error}
in case of validation failures. I also created an error callback that returns a 422 status code and halts Plug’s pipeline execution.
Now that we created the support files we should add them to test/test_helpers.exs
since all files under test/support/*
are not loaded by default.
So much code and still not a single line to implement the Plug’s functionality.
All I did until now is create a “client code” for my library. By starting from the client code I describe how I, as a developer, would have liked to use the library. For me, being able to feel how my library is going to be used before I even created it is priceless. It makes me focus on exactly what my library needs to do and puts the developer who uses it in top priority. It also allows me to iterate on the public interface of the library to make it as comfortable and natural as possible before I even write a single line of implementation code.
The last step before I go on to implement the functionality is to create a test against the dummy router. I decided to create all the tests at once and not to do it iteratively as TDD suggests because I felt they were trivial enough.
The tests are pretty straight forward. The first test case checks for a valid request and then there are a few examples of invalid parameters. The tests fail at this stage since I haven’t implemented anything yet. If I run the tests now I get the following error message:
** (UndefinedFunctionError) function Plug.Validator.init/1 is undefined or private
That’s because the only method we have in Plug.Validator
is the default hello
method generated by mix
. The minimal plug module implementation is the following one:
If we run the tests with this plug implementation the first test case passes but all the other cases fail since no validation is being performed yet.
The validation logic is pretty simple:
- Collect all invalid parameters by running the validations against the given
conn
parameters. - If the invalidation set is empty return the
conn
for further processing. - Otherwise apply the given error callback on
conn
and the invalidation set.
Let’s see the final Plug module:
This code satisfies all of our test cases.
If you would like to try your package as a dependency before publishing it to Hex you can use your local source code in the following way:{:plug_validator, path: “path/to/plug_validator”}
This allows you to use your code as if it was a package on Hex. Since this is the first package I wrote it was a nice validation before actually publishing it.
All Done
That’s it. All there’s left to do is to publish your package to hex and use it in your dependencies.
I’d be happy to get idea on how I could improve the code/process of this library.
Links
- Full source code GitHub repository
- plug_validator Hex package
- Module documentation
Thoughts / Final Words
- Should a plug library be tested in context of a dummy router as I did or in a more isolated way?
- Only after the fact I found out that it is a best practice to use the namespace
PlugValidator
and notPlug.Validator
since thePlug.*
namespace should be saved for the original library. - Things I didn’t cover here are how to publish to Hex and writting module documentation. Here is one of the many good guides for that. The GitHub repo itself includes the docs and you can view them here.
- Excuse me for incorrectness in describing/following TDD. I think every developer takes the paradigm to their own place. I know what I described isn’t exactly pure TDD but that’s where I find my balance to be as productive as possible.