restful - REST APIs made easy


 

Abstract

The code is licensed under the MIT license.

Installation shortcut: (ql:quickload :restful)


 

Contents

  1. A basic example
  2. The restful dictionary
    1. acceptor
    2. collection
    3. create-resource
    4. delete-item
    5. delete-resource
    6. get-item
    7. get-items
    8. has-permission
    9. load-resource
    10. memory-storage
    11. parent
    12. patch-resource
    13. permission-rejected
    14. replace-resource
    15. request-data-missing
    16. resource
    17. resource-action
    18. resource-metaclass
    19. resource-not-found-error
    20. save-item
    21. storage
    22. view-collection
    23. view-resource
  3. Acknowledgements

 

A basic example

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 

 

The restful dictionary


[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:

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:

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:


[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.


 

Acknowledgements

This documentation was prepared with DOCUMENTATION-TEMPLATE.