Current section
Files
Jump to
Current section
Files
lib/plug/cache_control/header.ex
defmodule Plug.CacheControl.Header do
@moduledoc false
@typep maybe(t) :: t | nil
@type t :: %__MODULE__{
must_revalidate: maybe(boolean()),
no_cache: maybe(boolean()),
no_store: maybe(boolean()),
no_transform: maybe(boolean()),
proxy_revalidate: maybe(boolean()),
private: maybe(boolean()),
public: maybe(boolean()),
max_age: maybe(integer()),
s_maxage: maybe(integer()),
stale_while_revalidate: maybe(integer()),
stale_if_error: maybe(integer())
}
defstruct [
:must_revalidate,
:no_cache,
:no_store,
:no_transform,
:proxy_revalidate,
:private,
:public,
:max_age,
:s_maxage,
:stale_while_revalidate,
:stale_if_error
]
@spec new() :: t()
def new, do: %__MODULE__{}
@spec new(Enum.t()) :: t()
def new(directives) do
put_many(%__MODULE__{}, directives)
end
@spec put(t(), {atom(), term()}) :: t()
def put(%__MODULE__{} = header, {directive, value}) do
do_put(header, directive, value)
end
@spec put(t(), atom(), term()) :: t()
def put(%__MODULE__{} = header, directive, value) do
do_put(header, directive, value)
end
@spec put_many(t(), Enum.t()) :: t()
def put_many(%__MODULE__{} = header, directives) do
Enum.reduce(directives, header, fn directive, header -> put(header, directive) end)
end
defp do_put(header, :public, value) do
%{header | public: value, private: !value}
end
defp do_put(header, :private, value) do
%{header | public: !value, private: value}
end
defp do_put(header, :no_cache, fields) when is_list(fields) do
joined_fields = Enum.join(fields, ", ")
%{header | no_cache: "\"#{joined_fields}\""}
end
defp do_put(header, directive, {_, _} = duration) do
do_put(header, directive, duration_to_seconds(duration))
end
defp do_put(header, directive, value) do
struct_put!(header, directive, value)
end
@spec from_string(String.t()) :: t()
def from_string(str) when is_binary(str) do
str
|> String.split(",", trim: true)
|> Enum.map(&String.trim/1)
|> Enum.map(&String.split(&1, "=", trim: true))
|> Enum.map(fn
[key] -> {directive_to_atom(key), true}
[key, value] -> {directive_to_atom(key), value}
end)
|> new()
end
@spec to_string(t()) :: String.t()
def to_string(%__MODULE__{} = header) do
Kernel.to_string(header)
end
# Header-specific utility functions
defp duration_to_seconds({period, unit}) when unit in [:second, :seconds], do: period
defp duration_to_seconds({period, unit}) when unit in [:minute, :minutes], do: period * 60
defp duration_to_seconds({period, unit}) when unit in [:hour, :hours], do: period * 60 * 60
defp duration_to_seconds({period, unit}) when unit in [:day, :days], do: period * 60 * 60 * 24
defp duration_to_seconds({period, unit}) when unit in [:week, :weeks],
do: period * 60 * 60 * 24 * 7
defp duration_to_seconds({period, unit}) when unit in [:year, :years],
do: period * 60 * 60 * 24 * 365
defp directive_to_atom(directive) when is_binary(directive) do
directive
|> String.replace("-", "_")
|> String.to_existing_atom()
end
defp struct_put!(struct, directive, value) when is_atom(directive) do
to_merge = Map.put(%{}, directive, value)
struct!(struct, to_merge)
end
defimpl String.Chars do
def to_string(header) do
header
|> Map.from_struct()
|> Enum.reduce([], fn
{_key, nil}, acc -> acc
{_key, false}, acc -> acc
{key, true}, acc -> [atom_to_directive(key) | acc]
{key, value}, acc -> ["#{atom_to_directive(key)}=#{value}" | acc]
end)
|> Enum.join(", ")
end
defp atom_to_directive(atom) when is_atom(atom) do
atom
|> Atom.to_string()
|> String.replace("_", "-")
end
end
end