Mix.install([
  {:curl_req, github: "derekkraan/curl_req", override: true},
  {:merquery, "~> 0.3.0"},
  {:kino, "~> 0.14.2"},
])

From cURL to Req

To generate a Req struct from a cURL command, you can either use the function CurlReq.from_curl/1 or the sigil_CURL.

import CurlReq

sigil = ~CURL(https://httpbin.org/get)
function = CurlReq.from_curl("curl https://httpbin.org/get")

Both produce the same Req.Request but the macro checks the validity at compile time and you don't have to escape the string. As you can see, the cURL command itself ("curl") gets ignored and produces the same request.

sigil === function

Let's see if CurlReq can understand a more complex cURL command

~CURL(curl -H "Accept-Encoding: gzip" -H "User-Agent: firefox/111" https://httpsbin.org/get)

As we can see, the User-Agent ist stored in the header map but where is the Accept-Encoding header? If you look closely at the Req.Request struct, you can see that a new request step was added (&Req.Steps.compressed/1). CurlReq tries to use/translate the native functionalities of the underlying HTTP client, in this case Req.

From Req to cURL

req = Req.new(base_url: "http://httpbin.org", url: "/post", method: :post, json: %{foo: "bar"})
curl = CurlReq.to_curl(req)

To read it better we can hide the double string escaping with some Kino helpers

Kino.Shorts.text(curl)

As you can see, the correct Accept headers get set and the JSON body gets correctly escaped.

Flag style

You can control if you want short or long flags in the generated cURL command

CurlReq.to_curl(req, flags: :long)

Flavor

You can control if you wan't to use the Req user agent instead of the native cURL command and if you want to set the implicit headers explicitly with this opion

CurlReq.to_curl(req, flavor: :req)

Third party integration

CurlReq is used in Merquery which describes itself as "Powered by the wonderful Req library, Merquery is an interactive and extensible HTTP client for Elixir and Livebook"

If you select the Plugins tab in the Smart Cell below you can activate the CurlReq plugin. This will log the request you make as a cURL command as you make the request. Just enter an URL and evaluate the cell, it will print the cURL command and after that the Req.Response.

req = Req.new(method: :get, url: "", headers: %{}, params: %{})
{req, resp} = Req.request(req)
resp

Another cool feature of merquery is, that you can import your cURL commands directly. Just copy the following request

curl --compressed -H "foo: bar" -X GET http://httpbin.org/get

and select the import icon from the top right of the Merquery cell below and paste it in the text area which will open.

req = Req.new(method: :get, url: "", headers: %{}, params: %{})
{req, resp2} = Req.request(req)
resp2
>>>>>>> Stashed changes

Advanced Usage

Internally everything gets converted to a CurlReq.Request struct. So you can modify it and generate the cURL and Req commands from that. Say we wouldn't know how to set the user agent with the cURL command. The solution would be to add it afterwards with CurlReq.Request.put_user_agent/2 and then encode it as a Req struct.

  request = CurlReq.Curl.decode("curl -k -X POST https://example.com")
request = CurlReq.Request.put_user_agent(request, "my_user_agent/1.0")
CurlReq.Req.encode(request)

You could also implement the CurlReq.Request behaviour for other HTTP clients. Here's an example for a non existing fake client.

defmodule MyHTTPClient do
  defstruct [:url, method: :get]
  @behaviour CurlReq.Request
  import CurlReq.Request

  @impl CurlReq.Request
  def decode(%__MODULE__{} = request, _opts \\ []) do
    %CurlReq.Request{}
    |> put_method(request.method)
    |> put_url(request.url)
  end

   @impl CurlReq.Request
  def encode(%CurlReq.Request{} = request, _opts \\ []) do
    %__MODULE__{url: URI.to_string(request.url), method: request.method}
  end
end
%MyHTTPClient{url: "https://example.com", method: :post} 
|> MyHTTPClient.decode() 
|> CurlReq.Curl.encode()
"curl -X PUT https://example.com"
|> CurlReq.Curl.decode()
|> MyHTTPClient.encode()