Repository: derekkraan/curl_req (elixir)

Issue:
`from_curl/1` does not return fully formed `Req.Request`
Hey @derekkraan 👋 

I ran into a small issue with `CurlReq.from_curl/1`: It does not return a fully formed `Req.Request` and I can't set certain request options on it because they weren't registered. 

For example, this shouldn't raise:

```elixir
"some curl command"
|> CurlReq.from_curl()
|> Req.merge(receive_timeout: 15_000)
** (ArgumentError) unknown option :receive_timeout
    (req 0.5.8) lib/req/request.ex:1175: Req.Request.validate_options/2
    (req 0.5.8) lib/req.ex:528: Req.merge/2
    iex:10: (file)
```

It works fine with regular `Req.Request` structs:

```
Req.new()
|> Req.merge(receive_timeout: 15_000)
```

The problem is that you create a `%Req.Request` struct by hand [here](https://github.com/derekkraan/curl_req/blob/5ca02982a65dfa0f0064fbdec9580d925c6e2bc5/lib/curl_req/req.ex#L75) instead of using `Req.new/0`. If you don't use the `new/0` function, the [Req.Steps.attach/1](https://github.com/wojtekmach/req/blob/ba4de2a7deb0085a74617faf3ee099074ce9b541/lib/req/steps.ex#L19) callback is never called and all the fancy options of Req are not registered (see the `Req.Request.register_options/2` call in `attach/1`).

You can verify this by running `req.registered_options` on the `Req.Request` struct returned by `CurlReq.from_curl/1`:

```elixir
iex> req = CurlReq.from_curl("some curl command")
%Req.Request{}
iex> req.registered_options
MapSet.new([:compressed])
```

If you create a request with `Req.new/0`, there are many more options registered:

```elixir
iex> req = Req.new()
%Req.Request{}
iex> req.registered_options
MapSet.new([:http_errors, :redirect_log_level, :follow_redirects, :pool_timeout,
 :redact_auth, :output, :range, :max_redirects, :cache_dir, :aws_sigv4,
 :receive_timeout, :cache, :decode_body, :user_agent, :plug, :path_params_style,
 :retry, :form_multipart, :retry_log_level, :params, :inet6, :unix_socket,
 :pool_max_idle_time, :form, :compressed, :json, :redirect_trusted,
 :decode_json, :auth, :finch_private, :max_retries, :redirect, :path_params,
 :raw, :finch, :compress_body, :checksum, :base_url, :connect_options,
 :finch_request, :retry_delay, :location_trusted])
```

Pull request:
Use `Req.new/0` instead of `%Req.Request{}`
Supersedes #52
Closes #51

The main change is still `Req.new/0` instead of `%Req.Request{}`

I've added some test assertion helpers to not have to match against the whole struct. This makes the test more concise and readable 

Patch:
diff --git a/lib/curl_req.ex b/lib/curl_req.ex
index 4099c5c..568b0c3 100644
--- a/lib/curl_req.ex
+++ b/lib/curl_req.ex
@@ -142,30 +142,23 @@ defmodule CurlReq do
 
   ## Examples
 
-      iex> CurlReq.from_curl("curl https://www.example.com")
-      %Req.Request{
-        method: :get, 
-        url: URI.parse("https://www.example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
-
-      iex> CurlReq.from_curl("curl -I https://example.com")
-      %Req.Request{
-        method: :head, 
-        url: URI.parse("https://example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
-
-      iex> CurlReq.from_curl("curl -b cookie_key=cookie_val https://example.com")
-      %Req.Request{
-        method: :get, 
-        headers: %{"cookie" => ["cookie_key=cookie_val"]}, 
-        url: URI.parse("https://example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
+      iex> req = CurlReq.from_curl("curl https://www.example.com")
+      iex> req.method
+      :get
+      iex> req.url
+      URI.parse("https://www.example.com")
+
+      iex> req = CurlReq.from_curl("curl -I https://example.com")
+      iex> req.method
+      :head
+      iex> req.url
+      URI.parse("https://example.com")
+
+      iex> req = CurlReq.from_curl("curl -b cookie_key=cookie_val https://example.com")
+      iex> req.method
+      :get
+      iex> Req.Request.get_header(req, "cookie")
+      ["cookie_key=cookie_val"]
   """
   @doc since: "0.98.4"
 
@@ -188,30 +181,23 @@ defmodule CurlReq do
 
   ## Examples
 
-      iex> ~CURL(curl "https://www.example.com")
-      %Req.Request{
-        method: :get, 
-        url: URI.parse("https://www.example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
-
-      iex> ~CURL(curl -I "https://example.com")
-      %Req.Request{
-        method: :head, 
-        url: URI.parse("https://example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
-
-      iex> ~CURL(curl -b "cookie_key=cookie_val" "https://example.com")
-      %Req.Request{
-        method: :get, 
-        headers: %{"cookie" => ["cookie_key=cookie_val"]}, 
-        url: URI.parse("https://example.com"), 
-        registered_options: MapSet.new([:connect_options]), 
-        options: %{connect_options: [protocols: [:http1, :http2]]}
-      }
+      iex> req = ~CURL(curl "https://www.example.com")
+      iex> req.method
+      :get
+      iex> req.url
+      URI.parse("https://www.example.com")
+
+      iex> req = ~CURL(curl -I "https://example.com")
+      iex> req.method
+      :head
+      iex> req.url
+      URI.parse("https://example.com")
+
+      iex> req = ~CURL(curl -b "cookie_key=cookie_val" "https://example.com")
+      iex> req.method
+      :get
+      iex> Req.Request.get_header(req, "cookie")
+      ["cookie_key=cookie_val"]
   """
   defmacro sigil_CURL(curl_command, modifiers)
 
diff --git a/lib/curl_req/curl.ex b/lib/curl_req/curl.ex
index 0f7345f..8cfd276 100644
--- a/lib/curl_req/curl.ex
+++ b/lib/curl_req/curl.ex
@@ -145,6 +145,8 @@ defmodule CurlReq.Curl do
     %CurlReq.Request{}
     |> CurlReq.Request.put_url(url)
     |> CurlReq.Request.put_encoding(:form)
+    |> CurlReq.Request.put_compression(false)
+    |> CurlReq.Request.put_redirect(false)
     |> add_header(options)
     |> add_body(options)
     |> add_cookie(options)
diff --git a/lib/curl_req/req.ex b/lib/curl_req/req.ex
index 3988438..67b447e 100644
--- a/lib/curl_req/req.ex
+++ b/lib/curl_req/req.ex
@@ -89,7 +89,7 @@ defmodule CurlReq.Req do
   @spec encode(CurlReq.Request.t()) :: Req.Request.t()
   def encode(%CurlReq.Request{} = request, _opts \\ []) do
     req =
-      %Req.Request{}
+      Req.new()
       |> Req.merge(url: request.url)
       |> Req.merge(method: request.method)
 
@@ -138,6 +138,9 @@ defmodule CurlReq.Req do
       case request.compression do
         :none ->
           req
+          |> Req.Request.register_options([:compressed])
+          |> Req.Request.prepend_request_steps(compressed: &Req.Steps.compressed/1)
+          |> Req.merge(compressed: false)
 
         _ ->
           req
@@ -150,6 +153,9 @@ defmodule CurlReq.Req do
       case request.redirect do
         false ->
           req
+          |> Req.Request.register_options([:redirect])
+          |> Req.Request.prepend_response_steps(redirect: &Req.Steps.redirect/1)
+          |> Req.merge(redirect: false)
 
         _ ->
           req
diff --git a/mix.exs b/mix.exs
index 64922f0..cafdb9a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -11,7 +11,8 @@ defmodule CurlReq.MixProject do
       package: package(),
       source_url: "https://github.com/derekkraan/curl_req",
       start_permanent: Mix.env() == :prod,
-      version: "0.100.0"
+      version: "0.100.0",
+      elixirc_paths: elixirc_paths(Mix.env())
     ]
   end
 
@@ -49,4 +50,7 @@ defmodule CurlReq.MixProject do
       maintainers: ["Derek Kraan", "Kevin Schweikert"]
     ]
   end
+
+  defp elixirc_paths(:test), do: ["lib", "test"]
+  defp elixirc_paths(_), do: ["lib"]
 end


Test patch:
diff --git a/test/assertions.ex b/test/assertions.ex
new file mode 100644
index 0000000..2ac4f6b
--- /dev/null
+++ b/test/assertions.ex
@@ -0,0 +1,99 @@
+defmodule CurlReq.Assertions do
+  import ExUnit.Assertions
+
+  def assert_url(%Req.Request{} = req, uri) when is_binary(uri) do
+    assert_url(req, URI.parse(uri))
+  end
+
+  def assert_url(%Req.Request{} = req, %URI{} = uri) do
+    assert req.url == uri, "Expected request URI #{inspect(req.url)} to equal #{inspect(uri)}"
+    req
+  end
+
+  def assert_option!(%Req.Request{} = req, option, value, opts \\ []) do
+    assert Req.Request.fetch_option!(req, option) == value, opts[:error] || []
+    req
+  end
+
+  def assert_option(%Req.Request{} = req, option, value, default \\ nil, opts \\ []) do
+    assert Req.Request.get_option(req, option, default) == value, opts[:error] || []
+    req
+  end
+
+  def assert_redirect(%Req.Request{} = req, allowed? \\ true) do
+    assert_option(req, :redirect, allowed?, true, error: "Expected redirect set to #{allowed?}")
+  end
+
+  def assert_compressed(%Req.Request{} = req, allowed? \\ true) do
+    assert_option(req, :compressed, allowed?, true,
+      error: "Expected compressed set to #{allowed?}"
+    )
+  end
+
+  def assert_form(%Req.Request{} = req, form) do
+    assert_option!(req, :form, form)
+  end
+
+  def assert_json(%Req.Request{} = req, json) do
+    assert_option!(req, :json, json)
+  end
+
+  def assert_auth(%Req.Request{} = req, :bearer, token) do
+    assert_option!(req, :auth, {:bearer, token})
+  end
+
+  def assert_auth(%Req.Request{} = req, :basic, userinfo) do
+    assert_option!(req, :auth, {:basic, userinfo})
+  end
+
+  def assert_auth(%Req.Request{} = req, :proxy, userinfo) do
+    connect_options = Req.Request.fetch_option!(req, :connect_options)
+    userinfo = Base.encode64(userinfo)
+
+    assert [{"proxy-authorization", "Basic " <> ^userinfo}] =
+             Keyword.fetch!(connect_options, :proxy_headers)
+  end
+
+  def assert_proxy(%Req.Request{} = req, scheme, host, port) when scheme in [:http, :https] do
+    connect_options = Req.Request.fetch_option!(req, :connect_options)
+    assert {^scheme, ^host, ^port, _} = Keyword.fetch!(connect_options, :proxy)
+    req
+  end
+
+  def assert_insecure(%Req.Request{} = req) do
+    connect_options = Req.Request.fetch_option!(req, :connect_options)
+    transport_opts = Keyword.fetch!(connect_options, :transport_opts)
+    assert :verify_none == Keyword.fetch!(transport_opts, :verify)
+    req
+  end
+
+  def assert_protocol(%Req.Request{} = req, protocol) when protocol in [:http1, :http2] do
+    connect_options = Req.Request.get_option(req, :connect_options, [])
+    protocols = Keyword.get(connect_options, :protocols, [:http1])
+    assert protocol in protocols
+    req
+  end
+
+  def assert_cookie(%Req.Request{} = req, cookie) when is_binary(cookie) do
+    case req.headers["cookie"] do
+      [cookies] ->
+        assert String.contains?(cookies, cookie)
+        req
+
+      other ->
+        flunk("Expected cookie header to contain #{cookie}, got: #{other}")
+    end
+  end
+
+  def assert_header(%Req.Request{} = req, name, value)
+      when is_binary(name) and is_list(value) do
+    assert Req.Request.get_header(req, name) == value
+    req
+  end
+
+  def assert_method(%Req.Request{} = req, method)
+      when method in [:get, :post, :put, :patch, :head] do
+    assert req.method == method
+    req
+  end
+end
diff --git a/test/case.ex b/test/case.ex
new file mode 100644
index 0000000..ce654be
--- /dev/null
+++ b/test/case.ex
@@ -0,0 +1,9 @@
+defmodule CurlReq.Case do
+  use ExUnit.CaseTemplate
+
+  using do
+    quote do
+      import CurlReq.Assertions
+    end
+  end
+end
diff --git a/test/curl_req_test.exs b/test/curl_req_test.exs
index 31bb1a3..daf2eb1 100644
--- a/test/curl_req_test.exs
+++ b/test/curl_req_test.exs
@@ -1,5 +1,5 @@
 defmodule CurlReqTest do
-  use ExUnit.Case, async: true
+  use CurlReq.Case, async: true
 
   doctest CurlReq, import: true
 
@@ -260,15 +260,15 @@ defmodule CurlReqTest do
 
   describe "from_curl" do
     test "no scheme in url defaults to http" do
-      assert ~CURL(curl example.com/fact) ==
-               %Req.Request{
-                 method: :get,
-                 url: URI.parse("http://example.com/fact"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl example.com/fact)
+      |> assert_url("http://example.com/fact")
+    end
+
+    test "redirect and compression is false by default" do
+      ~CURL(curl example.com/fact)
+      |> assert_url("http://example.com/fact")
+      |> assert_redirect(false)
+      |> assert_compressed(false)
     end
 
     test "wrong scheme raises error" do
@@ -280,413 +280,190 @@ defmodule CurlReqTest do
     end
 
     test "single header" do
-      assert ~CURL(curl -H "user-agent: req/0.4.14" -X GET https://example.com/fact) ==
-               %Req.Request{
-                 method: :get,
-                 url: URI.parse("https://example.com/fact"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl -H "foo: bar" -X GET https://example.com/fact)
+      |> assert_header("foo", ["bar"])
     end
 
     test "post method" do
-      assert ~CURL(curl -X POST https://example.com) ==
-               %Req.Request{
-                 method: :post,
-                 url: URI.parse("https://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl -X POST https://example.com)
+      |> assert_method(:post)
     end
 
     test "head method" do
-      assert ~CURL(curl -I https://example.com) ==
-               %Req.Request{
-                 method: :head,
-                 url: URI.parse("https://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl -I https://example.com)
+      |> assert_method(:head)
     end
 
     test "raw body" do
-      assert ~CURL(curl -d foo https://example.com) ==
-               %Req.Request{
-                 options: %{form: %{"foo" => ""}, connect_options: [protocols: [:http1, :http2]]},
-                 registered_options: MapSet.new([:form, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("https://example.com"),
-                 method: :post
-               }
+      ~CURL(curl -d foo https://example.com)
+      |> assert_form(%{"foo" => ""})
     end
 
     test "form body" do
-      assert ~CURL(curl -d foo=bar https://example.com) ==
-               %Req.Request{
-                 options: %{
-                   form: %{"foo" => "bar"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 registered_options: MapSet.new([:form, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("https://example.com"),
-                 method: :post
-               }
+      ~CURL(curl -d foo=bar https://example.com)
+      |> assert_form(%{"foo" => "bar"})
 
-      assert ~CURL(curl -d foo=bar&baz=qux https://example.com) ==
-               %Req.Request{
-                 options: %{
-                   form: %{"foo" => "bar", "baz" => "qux"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 registered_options: MapSet.new([:form, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("https://example.com"),
-                 method: :post
-               }
+      ~CURL(curl -d foo=bar&baz=qux https://example.com)
+      |> assert_form(%{"foo" => "bar", "baz" => "qux"})
     end
 
     test "form body with multiple data flags" do
-      assert ~CURL(curl http://example.com -d name=foo -d mail=bar) ==
-               %Req.Request{
-                 options: %{
-                   form: %{"name" => "foo", "mail" => "bar"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 registered_options: MapSet.new([:form, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("http://example.com"),
-                 method: :post
-               }
+      ~CURL(curl http://example.com -d name=foo -d mail=bar)
+      |> assert_form(%{"name" => "foo", "mail" => "bar"})
     end
 
     test "json body" do
-      assert ~CURL(curl -H "content-type: application/json" -d "{\"foo\": \"bar\"}" https://example.com) ==
-               %Req.Request{
-                 options: %{
-                   json: %{"foo" => "bar"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 registered_options: MapSet.new([:json, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("https://example.com"),
-                 method: :post
-               }
+      ~CURL(curl -H "content-type: application/json" -d "{\"foo\": \"bar\"}" https://example.com)
+      |> assert_json(%{"foo" => "bar"})
     end
 
     test "content-type with parameter" do
-      assert ~CURL(curl -H "content-type: application/json; charset=utf-8" -d '{"foo": "bar"}' https://example.com) ==
-               %Req.Request{
-                 options: %{
-                   json: %{"foo" => "bar"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 registered_options: MapSet.new([:json, :connect_options]),
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1],
-                 url: URI.parse("https://example.com"),
-                 method: :post
-               }
+      ~CURL(curl -H "content-type: application/json; charset=utf-8" -d '{"foo": "bar"}' https://example.com)
+      |> assert_json(%{"foo" => "bar"})
     end
 
     test "multiple header" do
-      assert ~CURL(curl -H "my-header: foo, bar=baz" https://example.com) ==
-               %Req.Request{
-                 url: URI.parse("https://example.com"),
-                 headers: %{"my-header" => ["foo, bar=baz"]},
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl -H "my-header: foo, bar=baz" https://example.com)
+      |> assert_header("my-header", ["foo, bar=baz"])
     end
 
     test "complex cookie" do
-      request =
-        ~CURL(curl --header 'Cookie: TealeafAkaSid=JA-JSAXRCLjKYhjV9IXTzYUbcV1Lnhqf; sapphire=1; visitorId=0184E4601D5A020183FFBB133 80347CE; GuestLocation=33196|25.660|-80.440|FL|US' -X GET https://example.com)
-
-      [cookie] = request.headers["cookie"]
-
-      assert String.contains?(cookie, "TealeafAkaSid=JA-JSAXRCLjKYhjV9IXTzYUbcV1Lnhqf")
-      assert String.contains?(cookie, "sapphire=1")
-      assert String.contains?(cookie, "visitorId=0184E4601D5A020183FFBB133 80347CE")
-      assert String.contains?(cookie, "GuestLocation=33196|25.660|-80.440|FL|US")
+      ~CURL(curl --header 'Cookie: TealeafAkaSid=JA-JSAXRCLjKYhjV9IXTzYUbcV1Lnhqf; sapphire=1; visitorId=0184E4601D5A020183FFBB133 80347CE; GuestLocation=33196|25.660|-80.440|FL|US' -X GET https://example.com)
+      |> assert_cookie("TealeafAkaSid=JA-JSAXRCLjKYhjV9IXTzYUbcV1Lnhqf")
+      |> assert_cookie("sapphire=1")
+      |> assert_cookie("visitorId=0184E4601D5A020183FFBB133 80347CE")
+      |> assert_cookie("GuestLocation=33196|25.660|-80.440|FL|US")
     end
 
     test "multiple headers with body" do
-      assert ~CURL(curl -H "accept-encoding: gzip" -H "authorization: Bearer 6e8f18e6-141b-4d12-8397-7e7791d92ed4:lon" -H "content-type: application/json" -H "user-agent: req/0.4.14" -d "{\"input\":[{\"leadFormFields\":{\"Company\":\"k\",\"Country\":\"DZ\",\"Email\":\"k\",\"FirstName\":\"k\",\"Industry\":\"CTO\",\"LastName\":\"k\",\"Phone\":\"k\",\"PostalCode\":\"1234ZZ\",\"jobspecialty\":\"engineer\",\"message\":\"I would like to know if Roche delivers to The Netherlands.\"}}],\"formId\":4318}" -X POST "https://example.com/rest/v1/leads/submitForm.json") ==
-               %Req.Request{
-                 method: :post,
-                 url: URI.parse("https://example.com/rest/v1/leads/submitForm.json"),
-                 registered_options: MapSet.new([:compressed, :auth, :json, :connect_options]),
-                 options: %{
-                   compressed: true,
-                   auth: {:bearer, "6e8f18e6-141b-4d12-8397-7e7791d92ed4:lon"},
-                   json: %{
-                     "formId" => 4318,
-                     "input" => [
-                       %{
-                         "leadFormFields" => %{
-                           "Company" => "k",
-                           "Country" => "DZ",
-                           "Email" => "k",
-                           "FirstName" => "k",
-                           "Industry" => "CTO",
-                           "LastName" => "k",
-                           "Phone" => "k",
-                           "PostalCode" => "1234ZZ",
-                           "jobspecialty" => "engineer",
-                           "message" =>
-                             "I would like to know if Roche delivers to The Netherlands."
-                         }
-                       }
-                     ]
-                   },
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:compressed, :auth, :encode_body],
-                 request_steps: [
-                   compressed: &Req.Steps.compressed/1,
-                   auth: &Req.Steps.auth/1,
-                   encode_body: &Req.Steps.encode_body/1
-                 ]
-               }
+      ~CURL(curl -H "accept-encoding: gzip" -H "authorization: Bearer 6e8f18e6-141b-4d12-8397-7e7791d92ed4:lon" -H "content-type: application/json" -H "user-agent: req/0.4.14" -d "{\"input\":[{\"leadFormFields\":{\"Company\":\"k\",\"Country\":\"DZ\",\"Email\":\"k\",\"FirstName\":\"k\",\"Industry\":\"CTO\",\"LastName\":\"k\",\"Phone\":\"k\",\"PostalCode\":\"1234ZZ\",\"jobspecialty\":\"engineer\",\"message\":\"I would like to know if Roche delivers to The Netherlands.\"}}],\"formId\":4318}" -X POST "https://example.com/rest/v1/leads/submitForm.json")
+      |> assert_url("https://example.com/rest/v1/leads/submitForm.json")
+      |> assert_method(:post)
+      |> assert_compressed()
+      |> assert_auth(:bearer, "6e8f18e6-141b-4d12-8397-7e7791d92ed4:lon")
+      |> assert_json(%{
+        "formId" => 4318,
+        "input" => [
+          %{
+            "leadFormFields" => %{
+              "Company" => "k",
+              "Country" => "DZ",
+              "Email" => "k",
+              "FirstName" => "k",
+              "Industry" => "CTO",
+              "LastName" => "k",
+              "Phone" => "k",
+              "PostalCode" => "1234ZZ",
+              "jobspecialty" => "engineer",
+              "message" => "I would like to know if Roche delivers to The Netherlands."
+            }
+          }
+        ]
+      })
     end
 
     test "without curl prefix" do
-      assert ~CURL(http://example.com) ==
-               %Req.Request{
-                 method: :get,
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(http://example.com)
+      |> assert_url("http://example.com")
     end
 
     test "cookie" do
-      assert ~CURL(http://example.com -b "name1=value1") ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 headers: %{"cookie" => ["name1=value1"]},
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(http://example.com -b "name1=value1")
+      |> assert_cookie("name1=value1")
 
-      assert ~CURL(http://example.com -b "name1=value1; name2=value2") ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 headers: %{"cookie" => ["name1=value1; name2=value2"]},
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(http://example.com -b "name1=value1; name2=value2")
+      |> assert_cookie("name1=value1")
+      |> assert_cookie("name2=value2")
     end
 
     test "formdata" do
-      assert ~CURL(curl http://example.com -F name=foo -F mail=bar) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 body: nil,
-                 registered_options: MapSet.new([:form, :connect_options]),
-                 options: %{
-                   form: %{"name" => "foo", "mail" => "bar"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1]
-               }
+      ~CURL(curl http://example.com -F name=foo -F mail=bar)
+      |> assert_form(%{"name" => "foo", "mail" => "bar"})
     end
 
     test "data raw" do
-      assert ~CURL"""
-             curl 'https://example.com/graphql' \
-             -X POST \
-             -H 'Accept: application/graphql-response+json'\
-             -H 'Content-Type: application/json' \
-             --data-raw '{"operationName":"get","query":"query get {name}"}'
-             """ ==
-               %Req.Request{
-                 method: :post,
-                 url: URI.parse("https://example.com/graphql"),
-                 headers: %{"accept" => ["application/graphql-response+json"]},
-                 registered_options: MapSet.new([:json, :connect_options]),
-                 options: %{
-                   json: %{"operationName" => "get", "query" => "query get {name}"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1]
-               }
+      ~CURL"""
+      curl 'https://example.com/graphql' \
+      -X POST \
+      -H 'Accept: application/graphql-response+json'\
+      -H 'Content-Type: application/json' \
+      --data-raw '{"operationName":"get","query":"query get {name}"}'
+      """
+      |> assert_method(:post)
+      |> assert_url("https://example.com/graphql")
+      |> assert_header("accept", ["application/graphql-response+json"])
+      |> assert_json(%{"operationName" => "get", "query" => "query get {name}"})
     end
 
     test "data raw with ansii escape" do
-      assert ~CURL"""
-             curl 'https://example.com/employees/107'\
-             -X PATCH\
-             -H 'Content-Type: application/json' \
-             -H 'Accept: application/vnd.api+json'\
-             --data-raw $'{"data":{"attributes":{"first-name":"Adam"}}}'
-             """ ==
-               %Req.Request{
-                 method: :patch,
-                 url: URI.parse("https://example.com/employees/107"),
-                 headers: %{"accept" => ["application/vnd.api+json"]},
-                 registered_options: MapSet.new([:json, :connect_options]),
-                 options: %{
-                   json: %{"data" => %{"attributes" => %{"first-name" => "Adam"}}},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:encode_body],
-                 request_steps: [encode_body: &Req.Steps.encode_body/1]
-               }
+      ~CURL"""
+      curl 'https://example.com/employees/107'\
+      -X PATCH\
+      -H 'Content-Type: application/json' \
+      -H 'Accept: application/vnd.api+json'\
+      --data-raw $'{"data":{"attributes":{"first-name":"Adam"}}}'
+      """
+      |> assert_method(:patch)
+      |> assert_url("https://example.com/employees/107")
+      |> assert_header("accept", ["application/vnd.api+json"])
+      |> assert_json(%{"data" => %{"attributes" => %{"first-name" => "Adam"}}})
     end
 
     test "auth" do
-      assert ~CURL(curl http://example.com -u user:pass) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 body: nil,
-                 registered_options: MapSet.new([:auth, :connect_options]),
-                 options: %{
-                   auth: {:basic, "user:pass"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:auth],
-                 request_steps: [auth: &Req.Steps.auth/1]
-               }
+      ~CURL(curl http://example.com -u user:pass)
+      |> assert_auth(:basic, "user:pass")
     end
 
     test "bearer token auth" do
-      curl = ~CURL"""
+      ~CURL"""
         curl -L \
         -H "Accept: application/vnd.github+json" \
         -H "Authorization: Bearer <YOUR-TOKEN>" \
         -H "X-GitHub-Api-Version: 2022-11-28" \
         https://example.com/users
       """
-
-      assert curl ==
-               %Req.Request{
-                 url: URI.parse("https://example.com/users"),
-                 body: nil,
-                 headers: %{
-                   "accept" => ["application/vnd.github+json"],
-                   "x-github-api-version" => ["2022-11-28"]
-                 },
-                 registered_options: MapSet.new([:auth, :redirect, :connect_options]),
-                 options: %{
-                   auth: {:bearer, "<YOUR-TOKEN>"},
-                   redirect: true,
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 current_request_steps: [:auth],
-                 request_steps: [auth: &Req.Steps.auth/1],
-                 response_steps: [redirect: &Req.Steps.redirect/1]
-               }
+      |> assert_header("accept", ["application/vnd.github+json"])
+      |> assert_header("x-github-api-version", ["2022-11-28"])
+      |> assert_auth(:bearer, "<YOUR-TOKEN>")
+      |> assert_redirect()
     end
 
     test "compressed" do
-      assert ~CURL(curl --compressed http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 body: nil,
-                 registered_options: MapSet.new([:compressed, :connect_options]),
-                 options: %{compressed: true, connect_options: [protocols: [:http1, :http2]]},
-                 current_request_steps: [:compressed],
-                 request_steps: [compressed: &Req.Steps.compressed/1]
-               }
+      ~CURL(curl --compressed http://example.com)
+      |> assert_compressed()
     end
 
     test "redirect" do
-      assert ~CURL(curl -L http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:redirect, :connect_options]),
-                 options: %{redirect: true, connect_options: [protocols: [:http1, :http2]]},
-                 response_steps: [redirect: &Req.Steps.redirect/1]
-               }
+      ~CURL(curl -L http://example.com)
+      |> assert_redirect()
+
+      ~CURL(curl --location http://example.com)
+      |> assert_redirect()
     end
 
     test "cookie, formadata, auth and redirect" do
-      assert ~CURL(curl -L -u user:pass -F name=foo -b name=bar http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 headers: %{"cookie" => ["name=bar"]},
-                 current_request_steps: [:auth, :encode_body],
-                 registered_options: MapSet.new([:redirect, :auth, :form, :connect_options]),
-                 options: %{
-                   redirect: true,
-                   auth: {:basic, "user:pass"},
-                   form: %{"name" => "foo"},
-                   connect_options: [protocols: [:http1, :http2]]
-                 },
-                 request_steps: [auth: &Req.Steps.auth/1, encode_body: &Req.Steps.encode_body/1],
-                 response_steps: [redirect: &Req.Steps.redirect/1]
-               }
+      ~CURL(curl -L -u user:pass -F name=foo -b name=bar http://example.com)
+      |> assert_redirect()
+      |> assert_auth(:basic, "user:pass")
+      |> assert_form(%{"name" => "foo"})
+      |> assert_cookie("name=bar")
     end
 
     test "proxy" do
-      assert ~CURL(curl --proxy my.proxy.com:22225 http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     proxy: {:http, "my.proxy.com", 22225, []},
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
+      ~CURL(curl --proxy my.proxy.com:22225 http://example.com)
+      |> assert_proxy(:http, "my.proxy.com", 22225)
     end
 
     test "proxy with basic auth" do
-      assert ~CURL(curl --proxy https://my.proxy.com:22225 --proxy-user foo:bar http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     proxy: {:https, "my.proxy.com", 22225, []},
-                     proxy_headers: [
-                       {"proxy-authorization", "Basic " <> Base.encode64("foo:bar")}
-                     ],
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
+      ~CURL(curl --proxy https://my.proxy.com:22225 --proxy-user foo:bar http://example.com)
+      |> assert_proxy(:https, "my.proxy.com", 22225)
+      |> assert_auth(:proxy, "foo:bar")
     end
 
     test "proxy with inline basic auth" do
-      assert ~CURL(curl --proxy https://foo:bar@my.proxy.com:22225 http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     proxy: {:https, "my.proxy.com", 22225, []},
-                     proxy_headers: [
-                       {"proxy-authorization", "Basic " <> Base.encode64("foo:bar")}
-                     ],
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
+      ~CURL(curl --proxy https://foo:bar@my.proxy.com:22225 http://example.com)
+      |> assert_proxy(:https, "my.proxy.com", 22225)
+      |> assert_auth(:proxy, "foo:bar")
     end
 
     test "proxy raises on non http scheme uri" do
@@ -700,181 +477,105 @@ defmodule CurlReqTest do
     end
 
     test "insecure flag" do
-      assert ~CURL(curl -k http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     transport_opts: [verify: :verify_none],
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
-
-      assert ~CURL(curl --insecure http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     transport_opts: [verify: :verify_none],
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
+      assert_insecure(~CURL(curl -k http://example.com))
+      assert_insecure(~CURL(curl --insecure http://example.com))
     end
 
     test "insecure flag with proxy" do
-      assert ~CURL(curl -k --proxy my.proxy.com:2233 http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [
-                     proxy: {:http, "my.proxy.com", 2233, []},
-                     transport_opts: [verify: :verify_none],
-                     protocols: [:http1, :http2]
-                   ]
-                 }
-               }
+      ~CURL(curl -k --proxy my.proxy.com:2233 http://example.com)
+      |> assert_insecure()
+      |> assert_proxy(:http, "my.proxy.com", 2233)
     end
 
     test "user agent flag" do
-      assert ~CURL(curl -A "some_user_agent/0.1.0" http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 headers: %{"user-agent" => ["some_user_agent/0.1.0"]},
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl -A "some_user_agent/0.1.0" http://example.com)
+      |> assert_header("user-agent", ["some_user_agent/0.1.0"])
 
-      assert ~CURL(curl --user-agent "some_user_agent/0.1.0" http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 headers: %{"user-agent" => ["some_user_agent/0.1.0"]},
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl --user-agent "some_user_agent/0.1.0" http://example.com)
+      |> assert_header("user-agent", ["some_user_agent/0.1.0"])
     end
 
     test "protocols flag" do
-      assert ~CURL(curl --http1.0 -X GET http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com")
-               }
+      ~CURL(curl --http1.0 -X GET http://example.com)
+      |> assert_protocol(:http1)
 
-      assert ~CURL(curl --http1.1 -X GET http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com")
-               }
+      ~CURL(curl --http1.1 -X GET http://example.com)
+      |> assert_protocol(:http1)
 
-      assert ~CURL(curl --http2 -X GET http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl --http2 -X GET http://example.com)
+      |> assert_protocol(:http2)
 
-      assert ~CURL(curl --http1.1 --http2 -X GET http://example.com) ==
-               %Req.Request{
-                 url: URI.parse("http://example.com"),
-                 registered_options: MapSet.new([:connect_options]),
-                 options: %{
-                   connect_options: [protocols: [:http1, :http2]]
-                 }
-               }
+      ~CURL(curl --http1.1 --http2 -X GET http://example.com)
+      |> assert_protocol(:http1)
+      |> assert_protocol(:http2)
     end
   end
 
   describe "newlines" do
     test "sigil_CURL supports newlines" do
-      curl = ~CURL"""
+      ~CURL"""
         curl -X POST \
          --location \
          https://example.com
       """
-
-      assert curl == %Req.Request{
-               method: :post,
-               url: URI.parse("https://example.com"),
-               registered_options: MapSet.new([:redirect, :connect_options]),
-               options: %{redirect: true, connect_options: [protocols: [:http1, :http2]]},
-               response_steps: [redirect: &Req.Steps.redirect/1]
-             }
+      |> assert_redirect()
+      |> assert_method(:post)
+      |> assert_url("https://example.com")
     end
 
     test "from_curl supports newlines" do
-      curl =
-        from_curl("""
-          curl -X POST \
-           --location \
-           https://example.com
-        """)
-
-      assert curl == %Req.Request{
-               method: :post,
-               url: URI.parse("https://example.com"),
-               registered_options: MapSet.new([:redirect, :connect_options]),
-               options: %{redirect: true, connect_options: [protocols: [:http1, :http2]]},
-               response_steps: [redirect: &Req.Steps.redirect/1]
-             }
+      from_curl("""
+        curl -X POST \
+         --location \
+         https://example.com
+      """)
+      |> assert_redirect()
+      |> assert_method(:post)
+      |> assert_url("https://example.com")
     end
 
     test "accepts newlines ending in backslash" do
-      uri = URI.parse("https://example.com/api/2024-07/graphql.json")
-
-      assert %Req.Request{
-               method: :post,
-               url: ^uri,
-               options: %{json: %{"query" => _}}
-             } = ~CURL"""
-                 curl -X POST \
-                   https://example.com/api/2024-07/graphql.json \
-                   -H 'Content-Type: application/json' \
-                   -H 'X-Shopify-Storefront-Access-Token: ABCDEF' \
-                   -d '{
-                     "query": "{
-                       products(first: 3) {
-                         edges {
-                           node {
-                             id
-                             title
-                           }
-                         }
-                       }
-                     }"
-                   }'
-             """
-
-      assert %Req.Request{
-               method: :post,
-               url: ^uri,
-               options: %{json: %{"query" => _}}
-             } = ~CURL"""
-                 curl -X POST
-                   https://example.com/api/2024-07/graphql.json
-                   -H 'Content-Type: application/json'
-                   -H 'X-Shopify-Storefront-Access-Token: ABCDEF'
-                   -d '{
-                     "query": "{
-                       products(first: 3) {
-                         edges {
-                           node {
-                             id
-                             title
-                           }
-                         }
-                       }
-                     }"
-                   }'
-             """
+      ~CURL"""
+          curl -X POST \
+            https://example.com/api/2024-07/graphql.json \
+            -H 'Content-Type: application/json' \
+            -H 'X-Shopify-Storefront-Access-Token: ABCDEF' \
+            -d '{
+              "query": "{
+                products(first: 3) {
+                  edges {
+                    node {
+                      id
+                      title
+                    }
+                  }
+                }
+              }"
+            }'
+      """
+      |> assert_url("https://example.com/api/2024-07/graphql.json")
+      |> assert_header("X-Shopify-Storefront-Access-Token", ["ABCDEF"])
+
+      ~CURL"""
+          curl -X POST
+            https://example.com/api/2024-07/graphql.json
+            -H 'Content-Type: application/json'
+            -H 'X-Shopify-Storefront-Access-Token: ABCDEF'
+            -d '{
+              "query": "{
+                products(first: 3) {
+                  edges {
+                    node {
+                      id
+                      title
+                    }
+                  }
+                }
+              }"
+            }'
+      """
+      |> assert_header("X-Shopify-Storefront-Access-Token", ["ABCDEF"])
+      |> assert_url("https://example.com/api/2024-07/graphql.json")
     end
 
     test "unused flags get ignored" do