From 2bee9c4c349fd5a8ba0d234bb044f2efc85620c1 Mon Sep 17 00:00:00 2001 From: Andrew Vy Date: Thu, 23 Feb 2017 17:05:52 -0800 Subject: [PATCH 1/2] :sparkles: Add HTTPipe.Conn.to_curl/1 This adds a new module called `HTTPipe.CurlHelpers` which converts a `%HTTPipe.Request{}` into a curl string that can then be executed into the command-line. - `HTTPipe.Conn.to_curl(request)` - `HTTPipe.Request.to_curl(request)` - `HTTPipe.CurlHelpers.convert_request_to_curl(request)` --- lib/httpipe/conn.ex | 11 +++++ lib/httpipe/curl_helpers.ex | 69 ++++++++++++++++++++++++++ lib/httpipe/request.ex | 13 ++++- test/httpipe/curl_helpers_test.exs | 79 ++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 lib/httpipe/curl_helpers.ex create mode 100644 test/httpipe/curl_helpers_test.exs diff --git a/lib/httpipe/conn.ex b/lib/httpipe/conn.ex index 16ace93..3f1a862 100644 --- a/lib/httpipe/conn.ex +++ b/lib/httpipe/conn.ex @@ -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 diff --git a/lib/httpipe/curl_helpers.ex b/lib/httpipe/curl_helpers.ex new file mode 100644 index 0000000..85ef500 --- /dev/null +++ b/lib/httpipe/curl_helpers.ex @@ -0,0 +1,69 @@ +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 + {:ok, full_url} = Request.prepare_url(base_url, params) + full_url + 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 diff --git a/lib/httpipe/request.ex b/lib/httpipe/request.ex index 8264c8c..ab96780 100644 --- a/lib/httpipe/request.ex +++ b/lib/httpipe/request.ex @@ -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 """ @@ -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. @@ -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 diff --git a/test/httpipe/curl_helpers_test.exs b/test/httpipe/curl_helpers_test.exs new file mode 100644 index 0000000..e2e959e --- /dev/null +++ b/test/httpipe/curl_helpers_test.exs @@ -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 From 617ff139bb241bb23e445cdfcba7b3821cff455c Mon Sep 17 00:00:00 2001 From: Andrew Vy Date: Thu, 23 Feb 2017 17:26:59 -0800 Subject: [PATCH 2/2] :zap: Add Curl String to Request.inspect Adds a new section for "Curl String" in the Request's inspect algebra doc. --- lib/httpipe/curl_helpers.ex | 6 ++++-- lib/httpipe/inspection_helpers.ex | 12 +++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/httpipe/curl_helpers.ex b/lib/httpipe/curl_helpers.ex index 85ef500..ee68e8f 100644 --- a/lib/httpipe/curl_helpers.ex +++ b/lib/httpipe/curl_helpers.ex @@ -26,8 +26,10 @@ defmodule HTTPipe.CurlHelpers do @spec convert_full_url(Request.url, Request.params) :: String.t def convert_full_url(base_url, params) do - {:ok, full_url} = Request.prepare_url(base_url, params) - full_url + case Request.prepare_url(base_url, params) do + {:ok, full_url} -> full_url + {:error, _} -> "" + end end @spec convert_method(Request.method) :: String.t diff --git a/lib/httpipe/inspection_helpers.ex b/lib/httpipe/inspection_helpers.ex index b1073d1..e840898 100644 --- a/lib/httpipe/inspection_helpers.ex +++ b/lib/httpipe/inspection_helpers.ex @@ -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"), @@ -86,7 +87,8 @@ defmodule HTTPipe.InspectionHelpers do full_url, headers, params, - body + body, + curl_string ] end @@ -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