Code Code Ship logo
 

CurlReq (a love letter to sigils)

June 3, 2024

Last week I was debugging an integration I had built with a 3rd party API. Nothing was going right, and to make matters worse, the error code I was getting back ("611 system error") wasn't helpful in the least. I needed to open a support ticket to proceed.

I use Req to make HTTP requests, but I can't just paste my %Req.Request{} struct into an email and expect the person on the other side (who has no knowledge of Elixir) to understand what is going on. So there I was, converting my request struct into an equivalent curl command when I thought "surely someone has built this as a library already!". Unfortunately this turned out not to be the case, so the obvious follow-up thought popped into my head, "gap in the market!" and I set out to build my own.

request
|> CurlReq.inspect(label: "MY_REQUEST")
|> Req.request!()

Getting a working to_curl/1 function was not really that hard. Adding inspect/2 makes it a lot more ergonomic, since now you can just toss in a line right before you make your request. This is quite useful and I expect it will make some people happy.

Have a library? Sell a pro version on Code Code Ship.
Read more

Now my itch has been scratched, and I invite whoever is reading this announcement to use this library if you find it useful, and if you find it lacking, or discover your own itches to scratch, to improve it with a PR! You can find the package here: GitHub, hex.pm, and hexdocs.


Now, there is also a certain symmetry to the situation. If I have a to_curl/1, then surely the library is not complete until I also have a from_curl/1 function (or equivalent). But I can do one better: I can make it into a sigil. Sigils are, in my opinion, one of the language's most fun features. You can extend the language to understand more literals. How cool is that!

But what is a literal? A literal is any text that the compiler can interpret and convert into a value. Here are some common examples of literals, each placed on its own line:

:hello            # atom
"hello"           # binary (UTF-8 string)
<<0>>             # binary
3.1416            # float
0xFFFF            # integer in hexadecimal notation
0o777             # integer in octal notation
0b11110000        # integer in binary notation
12345             # integer in decimal notation
[1, 2, 3, 4, 5]   # list
%{how_many?: "1"} # map
~r/[0-9]+/        # regular expression(!)

Notice that this looks like just bits of regular Elixir code. And you would be correct! Imagine what life would be like without a literal for strings though, especially UTF-8 strings. Today you can add the following UTF-8 string literal to your Elixir program, and it just works "🧌". The Elixir compiler knows about UTF-8 and it can turn your hastily pasted emoji into the necessary bits and bytes. In the absense of a string literal, you would have to construct your string in some other way, perhaps with the equivalent binary literal <<240, 159, 167, 140>>. That's a lot more work, and doesn't look like much fun at all! And it's the same for many many other things that you can write as a "literal" in your Elixir program, and let the compiler do the conversions for you. Literals just make life easier.

But wouldn't it be even better if you could extend the language and build your own literals? This is exactly what SIGILS are meant for.

Here are some sigils in the standard library that I really adore:

~r/[0-9]+/           # a Regular expression matching at least one number between 0 and 9.
~D"2024-06-03"       # today's date, which will become a %Date{} struct. See also ~U.
~S(here is a string) # an uninterpolated, unescaped string, with a broad assortment of delimiters!
~H(<html />)         # HTML templates; cheating a little: this one's from Phoenix

And now I have also extended the language by creating my own sigil:

~CURL[curl https://catfact.ninja/fact]
|> Req.request!()

This is actual code; you can run this. It will convert the curl command into a Req request and you will get a response back. This is really great, because we have been able to increase the expressiveness of the language. By doing this, Elixir is now able to work with CURL commands directly. Isn't this so much more expressive (or at least interesting) than the following code? At the very least, it is more performant, since the sigil is compiled down to a %Req.Request{} at compile-time.

"curl https://catfact.ninja/fact"
|> CurlReq.from_curl()
|> Req.request!()

Here are some other sigils that I have seen in the wild:

~LUA"x"      # which compiles LUA code using luerl into something the BEAM can use.
~CHATGPT"x"  # which compiles a description of your function into an actual function (for funsies only)

So now curl_req also includes the ~CURL sigil.

I hope that you have enjoyed this digression into literals and sigils. Maybe this has got your brain juices flowing, and you've got your own sigil in mind! In that case, do please make it into a library. Until next time!

By Derek Kraan
Code Code Ship helps you sell your library on a subscription basis.
Read more
Share
Code Code Ship logo

Get in touch

You can get in touch with us via email or on our Discord server. Click the button for an invite.

Subscribe

Always stay up to date by subscribing to our newsletter.