Abstract
The code is licensed under the MIT license.
Installation shortcut:
(ql:quickload :restful)
acceptor
collection
create-resource
delete-item
delete-resource
get-item
get-items
has-permission
load-resource
memory-storage
parent
patch-resource
permission-rejected
replace-resource
request-data-missing
resource
resource-action
resource-metaclass
resource-not-found-error
save-item
storage
view-collection
view-resource
An example of restful's usage can be found here: https://github.com/Ralt/restful-blog
Here is a gist of it:
(defclass article (restful:resource)
((slug :is-identifier t)
(title :required t)
(content :default ""))
(:metaclass restful:resource-metaclass))
(defclass comment (restful:resource)
((id :is-identifier t)
(commenter :required t)
(content :required t))
(:metaclass restful:resource-metaclass))
(hunchentoot:start
(make-instance 'restful:acceptor
:port 4444
:resource-definition {
"article" {
:class 'article
:collection 'restful:collection
:storage (make-instance 'restful:memory-storage)
:children {
"comment" {
:class 'comment
:collection 'restful:collection
:storage (make-instance 'restful:memory-storage)
}
}
}
}))
Here is an example of a couple of endpoints:
CL-USER> (drakma:http-request "http://localhost:4444/article"
:accept "application/json")
"[]"
200
CL-USER> (drakma:http-request "http://localhost:4444/article/foo"
:accept "application/json"
:method :put
:content "{\"slug\":\"foo\",\"title\":\"some article\"}")
"Created"
201
CL-USER> (drakma:http-request "http://localhost:4444/article/foo"
:accept "application/json"
:method :put
:content "{\"slug\":\"foo\",\"title\":\"some article\"}")
"No Content"
204
CL-USER> (drakma:http-request "http://localhost:4444/article/foo"
:accept "application/json")
"{\"slug\":\"foo\",\"title\":\"some article\",\"content\":\"\"}"
200
CL-USER> (drakma:http-request "http://localhost:4444/article/foo/comment"
:accept "application/json")
"[]"
200
CL-USER> (drakma:http-request "http://localhost:4444/article/foo/comment/bar"
:accept "application/json"
:method :put
:content "{\"id\":\"bar\",\"commenter\":\"foobar\",\"content\":\"test comment, pls ignore\"}")
"Created"
201
CL-USER> (drakma:http-request "http://localhost:4444/article/foo/comment/baz"
:accept "application/json"
:method :put
:content "{\"id\":\"baz\",\"commenter\":\"foobar\",\"content\":\"test comment, pls ignore\"}")
"Created"
201
CL-USER> (drakma:http-request "http://localhost:4444/article/foo/comment"
:accept "application/json")
"[{\"id\":\"baz\",\"commenter\":\"foobar\",\"content\":\"test comment, pls ignore\"},{\"id\":\"bar\",\"commenter\":\"foobar\",\"content\":\"test comment, pls ignore\"}]"
200
[Standard class]
acceptor
Base class for the acceptor, subclassing a hunchentoot acceptor to be able to handle incoming requests.
This class defines the following slot:
resource-definition
: defines the list of resources and how to handle them. This is a hash table that must:
- define a string key being the prefix for the resources
- for each key, define a value being a hash table. This hash table must:
- define a keyword key named
:class
being the resource class.- define a keyword named
:collection
being the collection class.- define a keyword named
:storage
being the storage instance.- define a keyword named
:children
being a new resource definition, if necessary.This is an example of a resource definition (assuming the readers macros to define hash tables using brackets):
{ "article" { :class 'article :collection 'restful:collection :storage (make-instance 'restful:memory-storage) :children { "comment" { :class 'comment :collection 'restful:collection :storage (make-instance 'restful:memory-storage) } } } }
The API will be available through the following endpoints:
/article
: collection endpoint./article/foo
: resource 'foo' of instancearticle
endpoint./article/foo/comment/bar
: resource 'bar' of instancecomment
, having for parent 'foo' of instancearticle
endpoint.This class should be used to instantiate objects to be used with hunchentoot.
Here is an example of such usage:
(hunchentoot:start (make-instance 'restful:acceptor :port 4242 :resource-definition *resource-definition*))
[Standard class]
collection
Base class for restful collections. There's not much reason to extend it with the current features.
[Generic function]
create-resource resource request-data => result
Creates a new resource based on the request data.
[Method]
create-resource (resource resource) request-data => result
Creates a new resource in the storage based on the request data.
[Generic function]
delete-item storage identifier => result
Deletes a single item in the storage.
[Method]
delete-item (storage memory-storage) identifier => result
Deletes the item from the hash table. The identifier is the key of the hash table.
[Generic function]
delete-resource resource => result
Deletes an existing resource.
[Method]
delete-resource (resource resource) => result
Deletes an existing resource. If the resource doesn't exist, an error was already thrown earlier thanks to load-resource.
[Generic function]
get-item storage identifier => result
Gets a single item in the storage. Returns a plist.
[Method]
get-item (storage memory-storage) identifier => result
Gets a single item according to its identifier. Returns a plist. The identifier is the key of the hash table.
[Generic function]
get-items storage => result
Gets all the items available in the storage.
This should be overridden for a specific storage if sorting/filtering options want to be added.
Returns a list of plists.
[Method]
get-items (storage memory-storage) => result
Gets all the items stored in the current instance.
[Generic function]
has-permission resource method => result
Determines if the request has permission to hit the resource. If it doesn't, returns NIL.
[Method]
has-permission (resource resource) method => result
Returns T. Override this method to change the behavior.
[Generic function]
load-resource resource => result
Loads a resource based on its identifier.
[Method]
load-resource (resource resource) => result
Loads the resource using its storage. A resource-not-found-error error is raised if the resource was not found.
[Standard class]
memory-storage
Restful's storage by storing the data in memory.
This is of course not very good for persistence matters (duh), but is perfect for development, and simply to show how a storage class can be built.
This storage just uses a hash table stored in a slot.
[Generic function]
parent object => result
[Method]
parent (object collection) => result
The parent resource, if any. If there's no parent, its value is NIL.
[Method]
parent (object resource) => result
Stores the parent of the resource in case of hierarchy. For example, if hitting foo/bar/baz/qux, the resource with identifier 'qux' will have 'bar' as parent. If there's no parent, its value is NIL.
[Generic function]
patch-resource resource request-data => result
Patches an existing resource based on the request data.
[Method]
patch-resource (resource resource) request-data => result
Patches an existing resource based on the request data.
[Condition type]
permission-rejected
Raised when a request doesn't have access to the requested resource.
This error is raised when the has-permission method of a resource returns NIL.
When this error is handled, the response will have the 403 status code.
This error can be raised with every method.
[Generic function]
replace-resource resource request-data => result
Replaces a resource based on the request data.
[Method]
replace-resource (resource resource) request-data => result
Replaces the resources based on the request data.
[Condition type]
request-data-missing
Raised when a request body doesn't fulfill the resource's schema.
For example, if a resource is the following:
(defclass foo (restful:resource) ((id :is-identifier t) (name :required t)) (:metaclass restful:resource-metaclass))
And the request body is the following:
{"id":"bar"}
This error will be raised.
When this error is handled, the response will have a 400 status code.
This error can be raised in the following requests: PUT, POST.
[Standard class]
resource
Base class for resources. All the resources should extend this class to have the default (required) slots.
[Generic function]
resource-action resource => result
Lets you handle actions on the resource. The :identifier slot lets you know which action is called. This method is called for the POST requests, and routing should be handled by yourself. Here is a typical example of what it can look like:
(defmethod restful:resource-action ((res custom-resource)) (cond ((string= (identifier res) "login") #'handle-login) (t (http-page-not-found))))
[Method]
resource-action (resource resource) => result
Returns a 404 Page Not Found. Raising a resource-not-found error doesn't make sense.
[Standard class]
resource-metaclass
The metaclass for resources, required to be used by all the resources.
This metaclass allows resources to use new slot options:
is-identifier
: defaults to NIL. Only one slot per resource should set this option to T. It will make the slot the identifier of the resource. The identifier is used to find the resource in the API. When set to T, the slot optionrequired
is implicitly set to T too.required
: defaults to NIL. When set to T, this slot will be required in the API requests.default
: defaults to""
. If the slot is not required, this value will be used to fill in the slot value if no value is provided.excluded
: defaults to NIL. When to set T, this slot will be ignored for the resource's CRUD actions. For example, therestful:resource
class uses it for itsparent
andstorage
slots.
[Condition type]
resource-not-found-error
Raised when a request resource is not found.
When this error is handled, the response will have the 404 status code.
This error can be raised in the following requests: GET, PATCH, DELETE, POST.
[Generic function]
save-item storage resource => result
Saves an item in the storage, creating it if needed.
Since resources are created or updated with PUT requests, as long as you have an ID, a resource exists. Except if the user doesn't have permission to PUT non-existing resources, but that's handled at the application level, not at the storage level, which only cares that a resource has an identifier.
[Method]
save-item (storage memory-storage) resource => result
Saves or updates an item in the hash table. Since
(setf (gethash [...]))
is used, the 'save or update' feature is very simply done.
[Generic function]
storage object => result
[Method]
storage (object collection) => result
The storage object that satisfies the interface of the
restful:storage
class.
[Method]
storage (object resource) => result
The storage object that satisfies the interface of the
restful:storage
class.
[Method]
storage (object memory-storage) => result
The hash table used to store the items in memory.
[Generic function]
view-collection collection => result
Returns an object that will be serialized to json using the jonathan library.
[Method]
view-collection (collection collection) => result
Returns an object populated by the collection's storage.
[Generic function]
view-resource resource => result
Returns an object that will be serialized to json using the jonathan library.
[Method]
view-resource (resource resource) => result
Returns a plist representing the resource.
This documentation was prepared with DOCUMENTATION-TEMPLATE.