defmodule ExCurl do
@moduledoc """
Documentation for `ExCurl`.
## Shared options
* `:headers` - a map of headers to include in the request, defaults to `%{"user-agent" => "curl/7.85.0"}`
* `:body` - a string to send as the request body, defaults to `""`
* `:follow_location` - if redirects should be followed, defaults to `true`
* `:ssl_verifyhost` - if SSL certificates should be verified, defaults to `true`
* `:ssl_verifypeer` - if SSL certificates should be verified, defaults to `true`
* `:return_metrics` - if request timing metrics should be included in the returned results, defaults to `false`
* `:verbose` - if curl should output verbose logs to stdout, useful for debugging. Defaults to `false`
* `:http_auth_negotiate` - if curl should use HTTP Negotiation (SPNEGO) as defined in [RFC 4559](https://datatracker.ietf.org/doc/html/rfc4559).
Note: this flag requires curl to be compiled with a suitable GSS-API or SSPI library. Defaults to `false`
## Error messages
Error messages refer to error codes on the [curl error codes documentation page](https://curl.se/libcurl/c/libcurl-errors.html).
For example, when we try to send a request to an invalid url:
iex> ExCurl.get("https://")
{:error, "URL_MALFORMAT"}
The returned error tuple includes the error message `"URL_MALFORMAT"`. This corresponds to the `CURLE_URL_MALFORMAT` (error code 3) error listed
in the [curl error codes documentation](https://curl.se/libcurl/c/libcurl-errors.html).
"""
alias ExCurl.{CurlErrorCodes, Request, RequestConfiguration, Response}
@doc """
Sends a GET request to the given `url`. Similar to `get/2` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
## Examples
iex> %ExCurl.Response{status_code: status_code} = ExCurl.get!("https://google.com")
iex> status_code
200
"""
def get!(url, opts \\ []), do: request!("GET", url, opts)
@doc """
Sends a POST request to the given `url`. Similar to `post/2` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
## Examples
iex> ExCurl.post!("https://httpbin.org/post", body: "some-value=true")
"""
def post!(url, opts \\ []), do: request!("POST", url, opts)
@doc """
Sends a PUT request to the given `url`. Similar to `put/2` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
## Examples
iex> ExCurl.put!("https://httpbin.org/put", headers: %{"content-type" => "application/json"}, body: "{}")
"""
def put!(url, opts \\ []), do: request!("PUT", url, opts)
@doc """
Sends a PATCH request to the given `url`. Similar to `patch/2` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
## Examples
iex> ExCurl.patch!("https://httpbin.org/patch", body: Jason.encode!(%{"some-value" => true}), headers: %{"content-type" => "application/json"})
"""
def patch!(url, opts \\ []), do: request!("PATCH", url, opts)
@doc """
Sends a DELETE request to the given `url`. Similar to `delete/2` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
## Examples
iex> ExCurl.delete!("https://httpbin.org/delete")
"""
def delete!(url, opts \\ []), do: request!("DELETE", url, opts)
@doc """
Sends a GET request to the given `url`.
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> {:ok, %ExCurl.Response{status_code: status_code}} = ExCurl.get("https://google.com", follow_location: false)
iex> status_code
301
iex> ExCurl.get("https://\\n\\n")
{:error, "URL_MALFORMAT"}
"""
def get(url, opts \\ []), do: request("GET", url, opts)
@doc """
Sends a POST request to the given `url`.
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> {:ok, %ExCurl.Response{body: body}} = ExCurl.post("https://httpbin.org/post", body: "some-value=true")
iex> Jason.decode!(body)["form"]
%{"some-value" => "true"}
iex> ExCurl.post("https://\\n\\n")
{:error, "URL_MALFORMAT"}
"""
def post(url, opts \\ []), do: request("POST", url, opts)
@doc """
Sends a PUT request to the given `url`.
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> ExCurl.put("https://httpbin.org/put", body: "some-value=true")
"""
def put(url, opts \\ []), do: request("PUT", url, opts)
@doc """
Sends a PATCH request to the given `url`.
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> ExCurl.patch("https://httpbin.org/patch", headers: %{"user-agent" => "custom-user-agent"})
"""
def patch(url, opts \\ []), do: request("PATCH", url, opts)
@doc """
Sends a DELETE request to the given `url`.
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> ExCurl.delete("https://httpbin.org/delete", headers: %{"authentication" => "bearer secret"})
"""
def delete(url, opts \\ []), do: request("DELETE", url, opts)
@doc """
Send a request to the `url` using the provided `method` and options
Returns either `{:ok, %ExCurl.Response{}}` or `{:error, "CURL_ERROR_MESSAGE"}`.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
See the ["Error messages"](#module-error-messages) section for details on error messages.
## Examples
iex> ExCurl.request("GET", "https://google.com")
iex> ExCurl.request("POST", "https://google.com")
"""
def request(method, url, opts \\ []) do
RequestConfiguration.build(method, url, opts)
|> do_request(opts)
|> case do
{:ok, resp} ->
{:ok, Response.from_keyword_list_response(resp)}
{:error, error_code} ->
{:error, CurlErrorCodes.get_message(error_code)}
end
end
@doc """
Sends a request to the given `url` using the provided `method` and options. Similar to `request/3` but raises `ExCurl.CurlError` if the request fails.
See the ["Shared options"](#module-shared-options) section at the module documentation for available options and their defaults.
"""
def request!(method, url, opts \\ []) do
case request(method, url, opts) do
{:ok, %Response{} = res} ->
res
{:error, error_message} when is_binary(error_message) ->
raise ExCurl.CurlError,
message: """
CURL Error: CURLE_#{error_message} (#{CurlErrorCodes.get_code(error_message)})
For more details, search for 'CURLE_#{error_message}' on https://curl.se/libcurl/c/libcurl-errors.html
"""
end
end
defp do_request(%RequestConfiguration{} = config, opts) do
json_config = Jason.encode!(config)
case Keyword.get(opts, :dirty_cpu, false) do
true -> Request.request_dirty_cpu(json_config)
_ -> Request.request(json_config)
end
end
end