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
126 changes: 125 additions & 1 deletion lib/req.ex
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ defmodule Req do

* `enumerable` - stream `enumerable` as request body

* `:assigns` - TODO

Additional URL options:

* `:base_url` - if set, the request URL is prepended with this base URL (via
Expand Down Expand Up @@ -532,7 +534,7 @@ defmodule Req do
IO.warn("Setting :redact_auth is deprecated and has no effect")
end

request_option_names = [:method, :url, :headers, :body, :adapter, :into]
request_option_names = [:method, :url, :headers, :body, :adapter, :into, :assigns]

{request_options, options} = Keyword.split(options, request_option_names)

Expand All @@ -556,6 +558,9 @@ defmodule Req do
{:headers, new_headers}, acc ->
update_in(acc.headers, &Req.Fields.merge(&1, new_headers))

{:assigns, assigns}, acc ->
update_in(acc.assigns, &Enum.into(assigns, &1))

{name, value}, acc ->
%{acc | name => value}
end)
Expand Down Expand Up @@ -1362,6 +1367,125 @@ defmodule Req do
Req.Fields.get_list(headers)
end

@doc """
Assigns multiple values to request/response.

## Examples

iex> req = Req.new()
iex> req.assigns
%{}
iex> req = Req.assign(req, a: 1, b: 2)
iex> req.assigns
%{a: 1, b: 2}
"""
@doc since: "0.6.0"
@spec assign(req_or_resp, Enumerable.t()) :: req_or_resp
when req_or_resp: Req.Request.t() | Req.Response.t()
def assign(%struct{} = req_or_resp, assigns) when struct in [Req.Request, Req.Response] do
update_in(req_or_resp.assigns, &Enum.into(assigns, &1))
end

@doc """
Assigns key/value to request/response.

## Examples

iex> req = Req.new()
iex> req.assigns
%{}
iex> req = Req.assign(req, :a, 1)
iex> req.assigns
%{a: 1}
"""
@doc since: "0.6.0"
@spec assign(req_or_resp, key :: atom(), value :: term()) :: req_or_resp
when req_or_resp: Req.Request.t() | Req.Response.t()
def assign(%struct{} = req_or_resp, key, value)
when is_atom(key) and struct in [Req.Request, Req.Response] do
update_in(req_or_resp.assigns, &Map.put(&1, key, value))
end

@doc """
Assigns key/value to request/response unless key is already set.

## Examples

iex> req = Req.new()
iex> req.assigns
%{}
iex> req = Req.assign_new(req, :a, 1)
iex> req.assigns
%{a: 1}
iex> req = Req.assign_new(req, :a, 2)
iex> req.assigns
%{a: 1}
"""
@doc since: "0.6.0"
@spec assign_new(req_or_resp, key :: atom(), value :: term()) :: req_or_resp
when req_or_resp: Req.Request.t() | Req.Response.t()
def assign_new(%struct{} = req_or_resp, key, value)
when is_atom(key) and struct in [Req.Request, Req.Response] do
update_in(req_or_resp.assigns, &Map.put_new(&1, key, value))
end

@doc """
Updates assign `key` in request/response with the given function.

Raises if the `key` is not set.

See also `update_assign/4`.

## Examples

iex> req = Req.new(assigns: [a: 1])
iex> req = Req.update_assign(req, :a, & &1 * 2)
iex> req.assigns
%{a: 2}

iex> req = Req.new(assigns: [a: 1])
iex> Req.update_assign(req, :b, & &1 * 2)
** (KeyError) key :b not found in: %{a: 1}
"""
@doc since: "0.6.0"
@spec update_assign(req_or_resp, key :: atom(), (term() -> term())) :: req_or_resp
when req_or_resp: Req.Request.t() | Req.Response.t()
def update_assign(%struct{} = req_or_resp, key, fun)
when struct in [Req.Request, Req.Response] and is_atom(key) and is_function(fun, 1) do
update_in(req_or_resp.assigns, &Map.update!(&1, key, fun))
end

@doc """
Updates assign `key` in request/response with the given function or `default`.

If `key` is present in assigns then the existing value is passed to fun and its
result is used as the updated value of `key`. If `key` is not present in assigns,
`default` is inserted as the value of `key`. The `default` value will not be passed
through the update function.

See also `update_assign/3`.

## Examples

iex> req = Req.new(assigns: [a: 1])
iex> req = Req.update_assign(req, :a, & &1 * 2)
iex> req.assigns
%{a: 2}

iex> req = Req.new(assigns: [a: 1])
iex> req = Req.update_assign(req, :b, 1, & &1 * 2)
iex> req.assigns
%{a: 1, b: 1}
"""
@doc since: "0.6.0"
@spec update_assign(req_or_resp, key :: atom(), default :: term(), (term() -> term())) ::
req_or_resp
when req_or_resp: Req.Request.t() | Req.Response.t()
def update_assign(%struct{} = req_or_resp, key, default, fun)
when struct in [Req.Request, Req.Response] and is_atom(key) and is_function(fun, 1) do
update_in(req_or_resp.assigns, &Map.update(&1, key, default, fun))
end

# Plugins support is experimental and undocumented.
defp run_plugins(request, [plugin | rest]) when is_atom(plugin) do
run_plugins(plugin.attach(request), rest)
Expand Down
8 changes: 7 additions & 1 deletion lib/req/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ defmodule Req.Request do
{:cont | :halt, {t, Req.Response.t()}})
| Collectable.t(),
options: options(),
assigns: map(),
halted: boolean(),
adapter: request_step(),
request_steps: [{name :: atom(), request_step()}],
Expand Down Expand Up @@ -424,6 +425,7 @@ defmodule Req.Request do
headers: Req.Fields.new([]),
body: nil,
options: %{},
assigns: %{},
halted: false,
adapter: &Req.Steps.run_finch/1,
request_steps: [],
Expand All @@ -450,6 +452,10 @@ defmodule Req.Request do

* `:adapter` - the request adapter, defaults to calling [`run_finch`](`Req.Steps.run_finch/1`).

* `:options` - TODO

* `:assigns` - TODO

## Examples

iex> req = Req.Request.new(url: "https://api.github.com/repos/wojtekmach/req")
Expand All @@ -463,7 +469,7 @@ defmodule Req.Request do
def new(options \\ []) do
options =
options
|> Keyword.validate!([:method, :url, :headers, :body, :adapter, :options])
|> Keyword.validate!([:method, :url, :headers, :body, :adapter, :options, :assigns])
|> Keyword.update(:url, URI.new!(""), &URI.parse/1)
|> Keyword.update(:headers, Req.Fields.new([]), &Req.Fields.new_without_normalize/1)
|> Keyword.update(:options, %{}, &Map.new/1)
Expand Down
6 changes: 5 additions & 1 deletion lib/req/response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ defmodule Req.Response do

* `:trailers` - the HTTP response trailers. The trailer names must be downcased.

* `:assigns` - TODO

* `:private` - a map reserved for libraries and frameworks to use.
Prefix the keys with the name of your project to avoid any future
conflicts. Only accepts `t:atom/0` keys.
Expand All @@ -23,13 +25,15 @@ defmodule Req.Response do
headers: %{optional(binary()) => [binary()]},
body: binary() | %Req.Response.Async{} | term(),
trailers: %{optional(binary()) => [binary()]},
assigns: map(),
private: map()
}

defstruct status: 200,
headers: Req.Fields.new([]),
body: "",
trailers: Req.Fields.new([]),
assigns: %{},
private: %{}

@doc """
Expand All @@ -52,7 +56,7 @@ defmodule Req.Response do

def new(%{} = options) do
options =
Map.take(options, [:status, :headers, :body, :trailers])
Map.take(options, [:status, :headers, :body, :trailers, :assigns])
|> Map.update(
:headers,
Req.Fields.new([]),
Expand Down
7 changes: 6 additions & 1 deletion test/req_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ defmodule ReqTest do
only: [
new: 1,
merge: 2,
get_headers_list: 1
get_headers_list: 1,
assign: 2,
assign: 3,
assign_new: 3,
update_assign: 3,
update_assign: 4
]

setup do
Expand Down
Loading