plug_rest v0.10.0 PlugRest.Resource behaviour

Define callbacks and REST semantics for a Resource behaviour

Based on Cowboy’s cowboy_rest module. It operates on a Plug connection and a handler module which implements one or more of the optional callbacks.

For example, the route:

resource "/users/:username", MyApp.UserResource

will invoke the init/2 function of MyApp.UserResource if it exists and then continue executing to determine the state of the resource. By default the resource must implement a to_html content handler which returns a “text/html” representation of the resource.

defmodule MyApp.UserResource do
  use PlugRest.Resource

  def init(conn, state) do
    {:ok, conn, state}
  end

  def allowed_methods(conn, state) do
    {["GET"], conn, state}
  end

  def resource_exists(%{params: params} = conn, _state)
    username = params["username"]
    # Look up user
    state = %{name: "John Doe", username: username}
    {true, conn, state}
  end

  def content_types_provided(conn, state) do
    {[{"text/html", :to_html}], conn, state}
  end

  def to_html(conn, %{name: name} = state) do
    {"<p>Hello, #{name}</p>", conn, state}
  end
end

Each callback accepts a %Plug.Conn{} struct and the current state of the resource, and returns a three-element tuple of the form {value, conn, state}.

The resource callbacks are named below, along with their default values. Some functions are skipped if they are undefined. Others have no default value.

allowed_methods        : ["GET", "HEAD", "OPTIONS"]
allow_missing_post     : true
charsets_provided      : skip
content_types_accepted : none
content_types_provided : [{{"text", "html", %{}}, :to_html}]
delete_completed       : true
delete_resource        : false
expires                : nil
forbidden              : false
generate_etag          : nil
is_authorized          : true
is_conflict            : false
known_methods          : ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
languages_provided     : skip
last_modified          : nil
malformed_request      : false
moved_permanently      : false
moved_temporarily      : false
multiple_choices       : false
options                : :ok
previously_existed     : false
resource_exists        : true
service_available      : true
uri_too_long           : false
valid_content_headers  : true
valid_entity_length    : true
variances              : []

You must also define the content handler callbacks that are specified through content_types_accepted/2 and content_types_provided/2. It is conventional to name the functions after the content types that they handle, such as from_html and to_html.

The handler function which provides a representation of the resource must return a three element tuple of the form {body, conn, state}, where body is one of:

  • binary(), which will be sent with send_resp/3
  • {:chunked, Enum.t}, which will use send_chunked/2
  • {:file, binary()}, which will use send_file/3

You can halt the resource handling from any callback and return a manual response like so:

response = send_resp(conn, status_code, resp_body)
{:stop, response, state}

The content accepted handlers defined in content_types_accepted will be called for POST, PUT, and PATCH requests. By default, the response body will be empty. If desired, you can set the response body like so:

conn2 = put_rest_body(conn, "#{conn.method} was successful")
{true, conn2, state}

Configuration

You can change some defaults by configuring the :plug_rest app in your config.exs file.

To change the default known_methods for all Resources:

config :plug_rest,
  known_methods: ["GET", "HEAD", "OPTIONS", "TRACE"]

If a Resource implements the known_methods callback, that list always takes precedence over the default list.

Plug Pipeline

You can create a custom Plug pipeline within your resource using Plug.Builder:

defmodule MessageResource do
  use PlugRest.Resource

  # Add the Builder to your resource
  use Plug.Builder

  # Add your custom plugs
  plug :hello

  # Finally, call the :rest plug to start executing the REST callbacks
  plug :rest

  # REST Callbacks
  def to_html(conn, state) do
    {conn.private.message, conn, state}
  end

  # Example custom plug function
  def hello(conn, _opts) do
    put_private(conn, :message, "Hello")
  end
end

Summary

Functions

Returns the requested media type

Returns the REST response body if it has been set

Puts the media type in the connection

Manually sets the REST response body in the connection

Executes the REST state machine with a connection and resource

Callbacks

Returns whether POST is allowed when the resource doesn’t exist

Returns the list of allowed methods

Returns the list of charsets the resource provides

Returns the list of content-types the resource accepts

Returns the list of content-types the resource provides

Returns whether the delete action has been completed

Deletes the resource

Returns the date of expiration of the resource

Returns whether access to the resource is forbidden

Returns the entity tag of the resource

Sets up the connection and handler state before other REST callbacks

Returns whether the user is authorized to perform the action

Returns whether the put action results in a conflict

Returns the list of known methods

Returns the list of languages the resource provides

Returns the date of last modification of the resource

Returns whether the request is malformed

Returns whether the resource was permanently moved

Returns whether the resource was temporarily moved

Returns whether there are multiple representations of the resource

Handles a request for information

Returns whether the resource existed previously

Returns whether the resource exists

Returns whether the service is available

Returns whether the requested URI is too long

Returns whether the content-* headers are valid

Returns whether the request body length is within acceptable boundaries

Return the list of headers that affect the representation of the resource

Types

conn :: Plug.Conn.t
content_handler :: PlugRest.State.content_handler
etag :: PlugRest.State.etag
etags_list :: PlugRest.Conn.etags_list
handler :: PlugRest.State.handler
media_type :: PlugRest.State.media_type
opts :: Plug.opts
priority_type :: PlugRest.Conn.priority_type
quality_type :: PlugRest.Conn.quality_type
state :: PlugRest.State.t
status_code :: 200..503

Functions

get_media_type(conn)

Specs

get_media_type(conn) :: media_type | nil

Returns the requested media type

get_rest_body(conn)

Specs

get_rest_body(conn) :: binary | nil

Returns the REST response body if it has been set

put_media_type(conn, media_type)

Specs

put_media_type(conn, media_type) :: conn

Puts the media type in the connection

put_rest_body(conn, resp_body)

Specs

put_rest_body(conn, binary) :: conn

Manually sets the REST response body in the connection

upgrade(conn, handler, handler_state)

Specs

upgrade(conn, handler, any) :: conn

Executes the REST state machine with a connection and resource

Accepts a Plug.Conn struct, a PlugRest.Resource handler, and the initial state of the handler, and executes the REST state machine.

Examples

Callbacks

allow_missing_post(conn, state) (optional)

Specs

allow_missing_post(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether POST is allowed when the resource doesn’t exist

Examples

def allow_missing_post(conn, state) do
  {true, conn, state}
end
allowed_methods(conn, state) (optional)

Specs

allowed_methods(conn, state) ::
  {[binary], conn, state} |
  {:stop, conn, state}

Returns the list of allowed methods

Examples

def allowed_methods(conn, state) do
  {["GET,", "HEAD", "OPTIONS"], conn, state}
end
charsets_provided(conn, state) (optional)

Specs

charsets_provided(conn, state) ::
  {[binary], conn, state} |
  {:stop, conn, state}

Returns the list of charsets the resource provides

Examples

def charsets_provided(conn, state) do
  {["utf-8"], conn, state}
end
content_types_accepted(conn, state) (optional)

Specs

content_types_accepted(conn, state) ::
  {[media_type], conn, state} |
  {:stop, conn, state}

Returns the list of content-types the resource accepts

The list must be ordered in order of preference.

Each content-type can be given either as a binary string or as a tuple containing the type, subtype and parameters.

Examples

def content_types_accepted(conn, state) do
  {[{"application/json", :from_json}], conn, state}
end

# post accepted
def from_json(conn, :success = state) do
  conn = put_rest_body(conn, "{\"status\": \"ok\"}")
  {true, conn, state}
end

# post create and redirect
def from_json(conn, :redirect = state) do
  {{true, "new_url/1234"}, conn, state}
end

# post error
def from_json(conn, :error = state) do
  {false, conn, state}
end
content_types_provided(conn, state) (optional)

Specs

content_types_provided(conn, state) ::
  {[content_type_p], conn, state} |
  {:stop, conn, state}

Returns the list of content-types the resource provides

Examples

def content_types_provided(conn, state) do
  {[{"application/json", :to_json}], conn, state}
end

def to_json(conn, state) do
  {"{}", conn, state}
end
delete_completed(conn, state) (optional)

Specs

delete_completed(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the delete action has been completed

Examples

def delete_completed(conn, state) do
  {true, conn, state}
end
delete_resource(conn, state) (optional)

Specs

delete_resource(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Deletes the resource

Examples

def delete_resource(conn, state) do
  {true, conn, state}
end
expires(conn, state) (optional)

Specs

expires(conn, state) ::
  {:calendar.datetime | binary | nil, conn, state} |
  {:stop, conn, state}

Returns the date of expiration of the resource

Examples

def expires(conn, state) do
  {{{2012, 9, 21}, {22, 36, 14}}, conn, state}
end
forbidden(conn, state) (optional)

Specs

forbidden(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether access to the resource is forbidden

Examples

def forbidden(conn, state) do
  {false, conn, state}
end
generate_etag(conn, state) (optional)

Specs

generate_etag(conn, state) ::
  {binary | {:weak | :strong, binary}, conn, state} |
  {:stop, conn, state}

Returns the entity tag of the resource

Examples

# ETag: W/"etag-header-value"
def generate_etag(conn, state) do
  {{:weak, "etag-header-value"}, conn, state}
end

# ETag: "etag-header-value"
def generate_etag(conn, state) do
  {{:strong, "etag-header-value"}, conn, state}
end

# ETag: "etag-header-value"
def generate_etag(conn, state) do
  {"\"etag-header-value\""}, conn, state}
end
init(conn, state) (optional)

Specs

init(conn, state) ::
  {:ok, conn, state} |
  {:stop, conn, state}

Sets up the connection and handler state before other REST callbacks

Examples

  def init(conn, state) do
    {:ok, conn, state}
  end
is_authorized(conn, state) (optional)

Specs

is_authorized(conn, state) ::
  {true | {false, binary}, conn, state} |
  {:stop, conn, state}

Returns whether the user is authorized to perform the action

Examples

def is_authorized(conn, state) do
  {true, conn, state}
end
is_conflict(conn, state) (optional)

Specs

is_conflict(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the put action results in a conflict

Examples

def is_conflict(conn, state) do
  {false, conn, state}
end
known_methods(conn, state) (optional)

Specs

known_methods(conn, state) ::
  {[binary], conn, state} |
  {:stop, conn, state}

Returns the list of known methods

Examples

def known_methods(conn, state) do
  {["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
   conn, state}
end
languages_provided(conn, state) (optional)

Specs

languages_provided(conn, state) ::
  {[binary], conn, state} |
  {:stop, conn, state}

Returns the list of languages the resource provides

Examples

def languages_provided(conn, state) do
  {["en"], conn, state}
end
last_modified(conn, state) (optional)

Specs

last_modified(conn, state) ::
  {:calendar.datetime, conn, state} |
  {:stop, conn, state}

Returns the date of last modification of the resource

Examples

def last_modified(conn, state) do
  {{{2012, 9, 21}, {22, 36, 14}}, conn, state}
end
malformed_request(conn, state) (optional)

Specs

malformed_request(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the request is malformed

Examples

def malformed_request(conn, state) do
  {false, conn, state}
end
moved_permanently(conn, state) (optional)

Specs

moved_permanently(conn, state) ::
  {{true, binary} | false, conn, state} |
  {:stop, conn, state}

Returns whether the resource was permanently moved

Examples

def moved_permanently(conn, state) do
  {false, conn, state}
end
moved_temporarily(conn, state) (optional)

Specs

moved_temporarily(conn, state) ::
  {{true, binary} | false, conn, state} |
  {:stop, conn, state}

Returns whether the resource was temporarily moved

Examples

def moved_temporarily(conn, state) do
  {false, conn, state}
end
multiple_choices(conn, state) (optional)

Specs

multiple_choices(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether there are multiple representations of the resource

Examples

def multiple_choices(conn, state) do
  {false, conn, state}
end
options(conn, state) (optional)

Specs

options(conn, state) ::
  {:ok, conn, state} |
  {:stop, conn, state}

Handles a request for information

The response should inform the client the communication options available for this resource.

By default, Cowboy will send a 200 OK response with the allow header set.

Examples

def options(conn, state) do
  {:ok, conn, state}
end
previously_existed(conn, state) (optional)

Specs

previously_existed(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the resource existed previously

Examples

def previously_existed(conn, state) do
  {false, conn, state}
end
resource_exists(conn, state) (optional)

Specs

resource_exists(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the resource exists

Examples

def resource_exists(conn, state) do
  {true, conn, state}
end
service_available(conn, state) (optional)

Specs

service_available(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the service is available

Examples

def service_available(conn, state) do
  {true, conn, state}
end
uri_too_long(conn, state) (optional)

Specs

uri_too_long(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the requested URI is too long

Examples

def uri_too_long(conn, state) do
  {false, conn, state}
end
valid_content_headers(conn, state) (optional)

Specs

valid_content_headers(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the content-* headers are valid

Examples

def valid_content_headers(conn, state) do
  {true, conn, state}
end
valid_entity_length(conn, state) (optional)

Specs

valid_entity_length(conn, state) ::
  {boolean, conn, state} |
  {:stop, conn, state}

Returns whether the request body length is within acceptable boundaries

Examples

def valid_entity_length(conn, state) do
  {true, conn, state}
end
variances(conn, state) (optional)

Specs

variances(conn, state) ::
  {[binary], conn, state} |
  {:stop, conn, state}

Return the list of headers that affect the representation of the resource

Examples

# vary: accept-language
def variances(conn, state) do
  {["accept-language"], conn, state}
end