README.md

# Frugality

A toolkit for handling conditional HTTP requests in Plug-based services.

Conditional HTTP requests are closely related to HTTP caching and are
useful in situations where one wants to:

* Prevent sending a response payload if the client has a cached
  representation that's still the same as on the server.

* Prevent an update if the client has an older version than the
  server.

## Usage

There are two modes of operation - automatic and manual.

### Automatic mode

The automatic mode can be activated by simply placing a plug in the
pipeline.

```elixir
plug Frugality.ConditionalGET
```

The plug generates ETag and last-modified headers if needed and
evaluates the request preconditions against the headers.

As you can see the automatic mode is pretty easy to use, easier than
the manual one, but the latter is more flexible.

### Manual mode

In manual mode, one is expected to define at least one additional
module - a metadata generator.

Let's say you have an endpoint serving individual orders and you want
to support conditional requests.

```elixir
defmodule OrderMetadata do
  @behaviour Frugality.Generator

  @impl true
  def etag(%{order: order}) do
    # For the purpose of the example, `order` is an Ecto schema struct.
    {:source, ["order", to_string(order.id), DateTime.to_iso8601(order.updated_at)]}
  end

  @impl true
  def last_modified(%{order: order}),
    do: order.updated_at
end
```

After we've defined a metadata generator, we can evaluate the request
preconditions in the controller.

```elixir
import Frugality,
  only: [put_generator: 2, evaluate_preconditions: 2]

plug :put_generator, OrderMetadata

def show(conn, %{"id" => order_id}) do
  order = Orders.get_order!(order_id)
  assigns = [order: order]

  with {:ok, conn} <- evaluate_preconditions(conn, assigns) do
    render(conn, :show, assigns)
  end
end
```

So far we've seen only `Frugality.evaluate_preconditions/2` being used
but there's another function which is applicable in many cases -
`Frugality.short_circuit/3`. Let's rewrite the previous example using
it.

```elixir
import Frugality,
  only: [put_generator: 2, short_circuit: 2]

plug :put_generator, OrderMetadata

def show(conn, %{"id" => order_id}) do
  order = Orders.get_order!(order_id)
  assigns = [order: order]

  short_circuit(conn, assigns, fn conn ->
    render(conn, :show, assigns)
  end)
end
```

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `frugality` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:frugality, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/frugality>.