Current section

Files

Jump to
curl_req lib curl_req.ex
Raw

lib/curl_req.ex

defmodule CurlReq do
@req_version :application.get_key(:req, :vsn) |> elem(1)
@flag_docs CurlReq.Curl.flags()
|> Enum.map(fn
{long, nil} -> "* `--#{long}`"
{long, short} -> "* `--#{long}`/`-#{short}`"
end)
|> Enum.join("\n")
@doc false
def req_version(), do: @req_version
@type inspect_opt :: {:label, String.t()}
@doc """
Inspect a Req struct in curl syntax.
Returns the unchanged `req`, just like `IO.inspect/2`.
## Examples
Req.new(url: "https://example.com")
|> CurlReq.inspect()
|> Req.request!()
#=> curl --compressed -X GET https://example.com
"""
@spec inspect(Req.Request.t(), [inspect_opt()]) :: Req.Request.t()
def inspect(req, opts \\ []) do
case Keyword.get(opts, :label) do
nil -> IO.puts(to_curl(req))
label -> IO.puts([label, ": ", to_curl(req)])
end
req
end
@spec step_names(Req.Request.t(), boolean()) :: [atom()]
defp step_names(%Req.Request{} = _req, false), do: []
defp step_names(%Req.Request{} = req, true), do: req.request_steps |> Keyword.keys()
@spec step_names(Req.Request.t(), [atom()]) :: [atom()]
defp step_names(%Req.Request{} = req, except: excludes) do
for {name, _} <- req.request_steps, name not in excludes do
name
end
end
defp step_names(%Req.Request{} = req, only: includes) do
for {name, _} <- req.request_steps, name in includes do
name
end
end
@spec run_steps(Req.Request.t(), [atom()]) :: Req.Request.t()
defp run_steps(req, steps) do
req.request_steps
|> Enum.filter(fn {step, _} ->
step in steps
end)
|> Enum.reduce(req, fn {step_name, step}, req ->
case step.(req) do
{_req, _response_or_error} ->
raise "The request was stopped by #{step_name} request_step."
next_req ->
next_req
end
end)
end
@doc """
Transforms a Req request into a curl command.
The following flags are supported:
#{@flag_docs}
Options:
- `run_steps`: Run the Req.Steps before generating the curl command to have fine-tuned control over the Req.Request. Default: `true`.
* `true`: Run all steps
* `false`: Run no steps
* `only: [atom()]`: A list of step names as atoms and only they will be executed
* `except: [atom()]`: A list of step names as atoms and these steps will be excluded from the executed steps
- `flags`: Specify the style the argument flags are constructed. Can either be `:short` or `:long`, Default: `:short`
- `flavor` or `flavour`: With the `:curl` flavor (the default) it will try to use native curl representations for compression, auth and will use the native user agent.
If flavor is set to `:req` the headers will not be modified and the curl command is constructed to stay as true as possible to the original `Req.Request`
## Examples
iex> Req.new(url: URI.parse("https://www.example.com"), compressed: true)
...> |> CurlReq.to_curl()
~S(curl --compressed -X GET https://www.example.com)
iex> Req.new(url: URI.parse("https://www.example.com"), compressed: true)
...> |> CurlReq.to_curl(flags: :long, flavor: :req)
~S(curl --header "accept-encoding: gzip" --user-agent "req/#{@req_version}" --request GET https://www.example.com)
iex> Req.new(url: "https://www.example.com")
...> |> CurlReq.to_curl(run_steps: [except: [:compressed]])
~S(curl -X GET https://www.example.com)
"""
@type flags :: :short | :long
@type flavor :: :curl | :req
@type to_curl_opts :: [
flags: flags(),
flavor: flavor(),
flavour: flavor(),
run_steps: boolean() | [only: [atom()]] | [except: [atom()]]
]
@spec to_curl(Req.Request.t(), to_curl_opts()) :: String.t()
def to_curl(req, options \\ []) do
options =
Keyword.validate!(options, flags: :short, run_steps: true, flavor: nil, flavour: :curl)
flavor = options[:flavor] || options[:flavour]
flags = options[:flags]
run_steps = options[:run_steps]
available_steps = step_names(req, run_steps)
req = run_steps(req, available_steps)
curl_options = [flavor: flavor, flags: flags]
CurlReq.Req.decode(req)
|> CurlReq.Curl.encode(curl_options)
end
@doc """
Transforms a curl command into a Req request.
The following flags are supported:
#{@flag_docs}
The `curl` command prefix is optional
> #### Info {: .info}
>
> Only string inputs are supported. That means for example `-d @data.txt` will not load the file or `-d @-` will not read from stdin
## Examples
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"
@spec from_curl(String.t()) :: Req.Request.t()
def from_curl(curl_command) do
curl_command
|> CurlReq.Curl.decode()
|> CurlReq.Req.encode()
end
@doc """
Same as `from_curl/1` but as a sigil. The benefit here is, that the `Req.Request` struct will be created at compile time and you don't need to escape the string.
Remember to
```elixir
require CurlReq
```
to use the custom sigil.
## Examples
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)
defmacro sigil_CURL({:<<>>, _line_info, [command]}, _extra) do
command
|> from_curl()
|> Macro.escape()
end
end