Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/httpipe/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -690,4 +690,15 @@ defmodule HTTPipe.Conn do

conn
end


@doc """
Converts the Conn struct's request into a valid curl string that
can be called from the command-line.
"""
@spec to_curl(t) :: String.t
def to_curl(conn) do
conn.request
|> Request.to_curl()
end
end
71 changes: 71 additions & 0 deletions lib/httpipe/curl_helpers.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
defmodule HTTPipe.CurlHelpers do
@moduledoc """
Helper module for formatting the `HTTPipe.Request` struct.

This module has helpers to format a valid curl string that can then be
executed from a command-line context.
"""

alias HTTPipe.Request

@doc """
Converts a Request struct into a curl string that can
be executed normally from the command-line.
"""
@spec convert_request_to_curl(Request.t) :: String.t
def convert_request_to_curl(request) do
full_url = convert_full_url(request.url, request.params)
method = convert_method(request.method)
headers = convert_headers(request.headers)
body = convert_body(request.body)

["curl", "#{method}", "#{full_url}", "#{headers}", "#{body}"]
|> Enum.reject(&(&1 == ""))
|> Enum.join(" ")
end

@spec convert_full_url(Request.url, Request.params) :: String.t
def convert_full_url(base_url, params) do
case Request.prepare_url(base_url, params) do
{:ok, full_url} -> full_url
{:error, _} -> ""
end
end

@spec convert_method(Request.method) :: String.t
def convert_method(method) do
curl_method = method |> Atom.to_string() |> String.upcase()
"-X #{curl_method}"
end

@spec convert_headers(Request.headers) :: String.t
def convert_headers(headers) do
headers
|> Enum.sort_by(&elem(&1, 0))
|> Enum.reduce([], fn({k, v}, acc) ->
[convert_header(k, v) | acc]
end)
|> Enum.join(" ")
end

@spec convert_header(String.t, String.t) :: String.t
def convert_header(k, v) do
"-H \"#{k}: #{v}\""
end

@spec convert_body(Request.body) :: String.t
def convert_body(nil), do: ""
def convert_body({:file, file_path}) do
"-d \"@#{file_path}\""
end
def convert_body({:form, keyword_list}) do
keyword_list
|> Enum.reduce([], fn(kv, acc) ->
["-F \"#{URI.encode_query([kv])}\"" | acc]
end)
|> Enum.join(" ")
end
def convert_body(body) when is_binary(body) do
"-d \"#{body}\""
end
end
12 changes: 11 additions & 1 deletion lib/httpipe/inspection_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ defmodule HTTPipe.InspectionHelpers do
full_url = inspect_full_url(request.url, request.params, opts)
params = inspect_params(request.params, opts)
body = inspect_body(request.body, opts)
curl_string = inspect_curl_string(request, opts)

concat [
format_section_head("Request"),
Expand All @@ -86,7 +87,8 @@ defmodule HTTPipe.InspectionHelpers do
full_url,
headers,
params,
body
body,
curl_string
]
end

Expand Down Expand Up @@ -211,6 +213,14 @@ defmodule HTTPipe.InspectionHelpers do
|> format_nested_with_header("Headers")
end

@spec inspect_curl_string(Request.t, Inspect.Opts.t) :: Inspect.Algebra.t
def inspect_curl_string(request, opts) do
request
|> Request.to_curl()
|> to_doc(opts)
|> format_nested_with_header("Curl String")
end

@spec inspect_status_code(Response.status_code, Inspect.Opts.t) :: Inspect.Algebra.t
def inspect_status_code(status_code, opts) do
status_code
Expand Down
13 changes: 11 additions & 2 deletions lib/httpipe/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule HTTPipe.Request do
structs and update internal `Request` structs.
"""

alias HTTPipe.InspectionHelpers
alias HTTPipe.{CurlHelpers, InspectionHelpers}
alias __MODULE__.{NilURLError}

@typedoc """
Expand All @@ -34,7 +34,7 @@ defmodule HTTPipe.Request do
"""
@type http_version :: String.t

@typdoc ~S"""
@typedoc ~S"""
Specifies a resource to access

The URL should include the scheme, domain, and request path.
Expand Down Expand Up @@ -542,4 +542,13 @@ defmodule HTTPipe.Request do

req
end

@doc """
Converts the Request struct into a valid curl string that
can be called from the command-line.
"""
@spec to_curl(t) :: String.t
def to_curl(request) do
CurlHelpers.convert_request_to_curl(request)
end
end
79 changes: 79 additions & 0 deletions test/httpipe/curl_helpers_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule HTTPipe.CurlHelpersTest do
use ExUnit.Case

alias HTTPipe.Request

test "Can encode URL" do
request =
%Request{}
|> Request.put_url("http://api.local/v1")

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X GET http://api.local/v1"
end

test "Can encode HTTP method" do
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_method(:post)

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X POST http://api.local/v1"
end

test "Can encode headers" do
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_header("Content-Type", "application/json")
|> Request.put_header("Accept-Encoding", "gzip")

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X GET http://api.local/v1 -H \"content-type: application/json\" -H \"accept-encoding: gzip\""
end

test "Can encode params into URL" do
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_param(:q, "httpipe elixir")

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X GET http://api.local/v1?q=httpipe+elixir"
end

test "Can encode body" do
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_body("{}")

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X GET http://api.local/v1 -d \"{}\""
end

test "Can encode form-based body" do
req_body = {:form, [q: "elixir strings", limit: 10]}
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_method(:post)
|> Request.put_body(req_body)

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X POST http://api.local/v1 -F \"limit=10\" -F \"q=elixir+strings\""
end

test "Can encode file-based body" do
req_body = {:file, "/tmp/testfile"}
request =
%Request{}
|> Request.put_url("http://api.local/v1")
|> Request.put_method(:post)
|> Request.put_body(req_body)

curl_string = request |> Request.to_curl()
assert curl_string == "curl -X POST http://api.local/v1 -d \"@/tmp/testfile\""
end
end