plug_rest v0.14.0 PlugRest.Resource behaviour View Source
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.UserResourcewill 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
endEach 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     : false
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
endLink to this section Summary
Types
The callback accepting a representation of the resource for a content-type
A WWW-Authenticate header value
A charset written in lowercase
A %Plug.Conn{} struct representing the connection
A content-type accepted handler, comprising a media type and acccept callback
A content-type provided handler, comprising a media type and provide callback
An entity tag
The name of an HTTP header
A language tag written in lowercase
A representation of a content-type match
An HTTP method written in uppercase
An ok callback value
The callback providing a representation of the resource for a content-type
A Module adopting the PlugRest.Resource behaviour
The state of the resource
A stop callback value
A URI
Functions
Returns the REST response body if it has been set
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
Link to this section Types
The callback accepting a representation of the resource for a content-type
A WWW-Authenticate header value
A charset written in lowercase
A %Plug.Conn{} struct representing the connection
content_type_a()
View Sourcecontent_type_a() :: {binary() | media_type(), accept_resource()}
      A content-type accepted handler, comprising a media type and acccept callback
content_type_p()
View Sourcecontent_type_p() :: {binary() | media_type(), provide_resource()}
      A content-type provided handler, comprising a media type and provide callback
An entity tag
Examples
# ETag: W/"etag-header-value"
{:weak, "etag-header-value"}
# ETag: "etag-header-value"
{:strong, "etag-header-value"}
# ETag: "etag-header-value"
{"\"etag-header-value\""}The name of an HTTP header
A language tag written in lowercase
A representation of a content-type match
An HTTP method written in uppercase
An ok callback value
The callback providing a representation of the resource for a content-type
A Module adopting the PlugRest.Resource behaviour
The state of the resource
A stop callback value
A URI
Link to this section Functions
Returns the REST response body if it has been set
Manually sets the REST response body in the connection
Executes the REST state machine with a connection and resource
Accepts a Plug.Conn struct, a PlugRest.Resource module, and the
initial state of the resource, and executes the REST state machine.
Link to this section Callbacks
Returns whether POST is allowed when the resource doesn't exist
- Methods: POST
- Default: false
This function will be called when resource_exists is false and
the request method is POST. Returning true means the missing
resource can process the enclosed representation, and the resource's
content accepted handler will be invoked.
Returning true means POST should update an existing resource and
create one if it is missing.
Returning false means POST to a missing resource will send 404 Not Found.
Examples
def allow_missing_post(conn, state) do
  {true, conn, state}
endReturns the list of allowed methods
- Methods: all
- Default: ["GET", "HEAD", "OPTIONS"]
Methods are case sensitive and should be given in uppercase.
If the request uses a method that is not allowed, the resource will
respond 405 Method Not Allowed.
Examples
def allowed_methods(conn, state) do
  {["GET,", "HEAD", "OPTIONS"], conn, state}
endReturns the list of charsets the resource provides
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: Skip to the next step if undefined.
The list must be ordered by priority.
The first charset will be chosen if the client does not send an accept-charset header, or the first that matches.
The charset should be returned as a lowercase string.
Examples
def charsets_provided(conn, state) do
  {["utf-8"], conn, state}
endReturns the list of content-types the resource accepts
- Methods: POST, PUT, PATCH
- Default: Crash if undefined.
The list must be ordered by priority.
Each content-type can be given either as a string like
"text/html"; or a tuple in the form {type, subtype, params},
where params can be %{} (no params acceptable), :* (all params
acceptable), or a map of acceptable params %{"level" => "1"}.
If no content types match, a 415 Unsupported Media Type response
will be sent.
Examples
def content_types_accepted(conn, state) do
  {[{"application/json", :from_json}], conn, state}
endThe content accepted handler value is the name of the callback that will be called if the content-type matches. It is defined as follows.
- Value type: true | {true, URL} | false
- Default: Crash if undefined.
Process the request body
This function should create or update the resource based on the
request body and the method used. Consult the Plug.Conn and
Plug.Parsers docs for information on parsing and reading the
request body params.
Returning true means the process was successful. Returning {true, URL} means a new resource was created at that location.
Returning false will send a 400 Bad Request response.
If a response body must be sent, the appropriate media-type, charset
and language can be manipulated using Plug.Conn. The body can be
set using put_rest_body/2.
Examples
# 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}
endReturns the list of content-types the resource provides
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: [{{"text", "html", %{}}, :to_html}]
The list must be ordered by priority.
Each content-type can be given either as a string like
"text/html"; or a tuple in the form {type, subtype, params},
where params can be %{} (no params acceptable), :* (all params
acceptable), or a map of acceptable params %{"level" => "1"}.
PlugRest will choose the content-type through content negotiation with the client.
If content negotiation fails, a 406 Not Acceptable response will
be sent.
Examples
def content_types_provided(conn, state) do
  {[{"application/json", :to_json}], conn, state}
endThe content provided handler names a function that will return a representation of the resource using that content-type. It is defined as follows.
- Methods: GET, HEAD
- Value type: binary() | {:chunked, Enum.t} | {:file, binary()}
- Default: Crash if undefined.
Return the response body.
Examples
def to_json(conn, state) do
  {"{}", conn, state}
endReturns whether the delete action has been completed
- Methods: DELETE
- Default: true
This function is called after a successful delete_resource.
Returning true means the delete has completed. Returning false
means the request was accepted but may not have finished, and
responds with 202 Accepted.
Examples
def delete_completed(conn, state) do
  {true, conn, state}
endDeletes the resource
- Methods: DELETE
- Default: false
Returning true means the delete request can be enacted. Returning
false will send a 500 error.
Examples
def delete_resource(conn, state) do
  {true, conn, state}
endReturns the date of expiration of the resource
- Methods: GET, HEAD
- Default: nil
This date will be sent as the value of the expires header. The date
can be specified as a datetime() tuple or a string.
Examples
def expires(conn, state) do
  {{{2012, 9, 21}, {22, 36, 14}}, conn, state}
endReturns whether access to the resource is forbidden
- Methods: all
- Default: false
Returning true will send a 403 Forbidden response.
Examples
def forbidden(conn, state) do
  {false, conn, state}
endReturns the entity tag of the resource
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: nil
This value will be sent as the value of the etag header.
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}
endSets up the connection and handler state before other REST callbacks
- Methods: all
- Default: :ok
Examples
  def init(conn, state) do
    {:ok, conn, state}
  endReturns whether the user is authorized to perform the action
- Methods: all
- Default: true
Returning {false, binary()} will send a 401 Unauthorized
response. The value of the binary() will be set as the
WWW-authenticate header.
Examples
def is_authorized(conn, state) do
  {true, conn, state}
endReturns whether the PUT action results in a conflict
- Methods: PUT
- Default: false
Returning true will send a 409 Conflict response.
Examples
def is_conflict(conn, state) do
  {false, conn, state}
endReturns the list of known methods
- Methods: all
- Default: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
Specifies the full list of HTTP methods known by the server, even if they aren't allowed in this resource.
The default list can be configured in config.exs:
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.
Methods are case sensitive and should be given in uppercase.
Examples
def known_methods(conn, state) do
  {["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
   conn, state}
endReturns the list of languages the resource provides
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: Skip to the next step if undefined.
The first language will be chosen if the client does not send an accept-language header, or the first that matches.
The language should be returned as a lowercase binary.
Examples
def languages_provided(conn, state) do
  {["en"], conn, state}
endReturns the date of last modification of the resource
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: nil
Returning a datetime() tuple will set the last-modified header and
be used for comparison in conditional if-modified-since and
if-unmodified-since requests.
Examples
def last_modified(conn, state) do
  {{{2012, 9, 21}, {22, 36, 14}}, conn, state}
endReturns whether the request is malformed
- Methods: all
- Default: false
Returning true will send a 400 Bad Request response.
Examples
def malformed_request(conn, state) do
  {false, conn, state}
endReturns whether the resource was permanently moved
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: false
Returning {true, URI} will send a 301 Moved Permanently response
with the URI in the Location header.
Examples
def moved_permanently(conn, state) do
  {{true, "/new_location"}, conn, state}
endReturns whether the resource was temporarily moved
- Methods: GET, HEAD, POST, PATCH, DELETE
- Default: false
Returning {true, URI} will send a 307 Temporary Redirect
response with the URI in the Location header.
Examples
def moved_temporarily(conn, state) do
  {{true, "/new_location"}, conn, state}
endReturns whether there are multiple representations of the resource
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: false
Returning true means that multiple representations of the resource
are possible and one cannot be chosen automatically. This will send
a 300 Multiple Choices response. The response body should include
information about the different representations using
set_rest_body/2. The content-type that was already negotiated can
be retrieved by calling:
[content-type] = get_resp_header(conn, "content-type")Examples
def multiple_choices(conn, state) do
  {false, conn, state}
endHandles a request for information
- Methods: OPTIONS
- Default: true
The response should inform the client the communication options available for this resource.
By default, PlugRest will send a 200 OK response with the list of
supported methods in the Allow header.
Examples
def options(conn, state) do
  {:ok, conn, state}
endReturns whether the resource existed previously
- Methods: GET, HEAD, POST, PATCH, DELETE
- Default: false
Returning true will invoke moved_permanently and
moved_temporarily to determine whether to send a 301 Moved Permanently, 307 Temporary Redirect, or 410 Gone response.
Examples
def previously_existed(conn, state) do
  {false, conn, state}
endReturns whether the resource exists
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: true
Returning false will send a 404 Not Found response, unless the
method is POST and allow_missing_post is true.
Examples
def resource_exists(conn, state) do
  {true, conn, state}
endReturns whether the service is available
- Methods: all
- Default: true
Use this to confirm all backend systems are up.
Returning false will send a 503 Service Unavailable response.
Examples
def service_available(conn, state) do
  {true, conn, state}
endReturns whether the requested URI is too long
- Methods: all
- Default: false
Returning true will send a 414 Request-URI Too Long response.
Examples
def uri_too_long(conn, state) do
  {false, conn, state}
endReturns whether the content-* headers are valid
- Methods: all
- Default: true
This functions should check for invalid or unknown content-* headers.
Returning false will send a 501 Not Implemented response.
Examples
def valid_content_headers(conn, state) do
  {true, conn, state}
endReturns whether the request body length is within acceptable boundaries
- Methods: all
- Default: true
Returning false will send a 413 Request Entity Too Large
response.
Examples
def valid_entity_length(conn, state) do
  {true, conn, state}
endReturn the list of headers that affect the representation of the resource
- Methods: GET, HEAD, POST, PUT, PATCH, DELETE
- Default: []
This function may return a list of strings saying which headers should be included in the response's Vary header.
PlugRest will automatically add the Accept, Accept-language and Accept-charset headers to the list if the respective functions were defined in the resource.
Examples
# vary: user-agent
def variances(conn, state) do
  {["user-agent"], conn, state}
end