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.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 : 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 withsend_resp/3
{:chunked, Enum.t}
, which will usesend_chunked/2
{:file, binary()}
, which will usesend_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
Link 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
The 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}
end
Returns 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}
end
The 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}
end
Returns 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}
end
Deletes 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Sets up the connection and handler state before other REST callbacks
- Methods: all
- Default:
:ok
Examples
def init(conn, state) do
{:ok, conn, state}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Handles 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Returns 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}
end
Return 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