Skip to content
This repository has been archived by the owner before Nov 9, 2022. It is now read-only.

valpackett/octohipster

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 

Current semantic version:

[octohipster "0.2.1-SNAPSHOT"]

octohipster Build Status unlicense

Octohipster is

It allows you to make APIs that

  • support hypermedia (HAL+JSON, Collection+JSON and Link/Link-Template HTTP headers; works with Frenetic)
  • support multiple output formats (JSON, EDN, YAML and any custom format)
  • have Swagger documentation
  • use JSON Schema for validation and documentation
  • have pagination

Concepts

  • a resource is a single endpoint that accepts requests and returns responses
  • a group is a collection of resources with a single URL prefix (eg. a group /things contains resources /things/ and /things/{id}) and zero or more shared properties (usually the schema)
  • a documenter is a function that returns a resource which documents regular resources (Swagger, HAL root, etc)
  • a mixin is a function that is applied to multiple resources to give them shared behavior (eg. collection or entry behavior)
  • a response handler is a function that is used to encode response data to a particular content-type (JSON, EDN, YAML, etc.)
  • a params handler is a function that is used to decode incoming data from a particular content-type (JSON, EDN, YAML, etc.)

Usage

(ns example
  (:use [octohipster core routes mixins pagination]
        [octohipster.documenters swagger schema]
        org.httpkit.server)
  (:import org.bson.types.ObjectId)
  (:require [monger.core :as mg]
            [monger.query :as mq]
            [monger.collection :as mc]
            monger.json))

(mg/connect!)
(mg/set-db! (mg/get-db "octohipster-example"))

;;;; The "model"
;;;;  tip: make it a separate namespace, eg. app.models.contact
(def contact-schema
  {:id "Contact"
   :type "object"
   :properties {:name {:type "string"}
                :phone {:type "integer"}}
   :required [:name]})

(defn contacts-count [] (mc/count "contacts"))
(defn contacts-all []
  (mq/with-collection "contacts"
    (mq/find {})
    (mq/skip *skip*)
    (mq/limit *limit*)))
(defn contacts-find-by-id [x] (mc/find-map-by-id "contacts" (ObjectId. x)))
(defn contacts-insert! [x]
  (let [id (ObjectId.)]
    (mc/insert "contacts" (assoc x :_id id))
    (mc/find-map-by-id "contacts" id)))
(defn contacts-update! [x old] (mc/update "contacts" old x :multi false))
(defn contacts-delete! [x] (mc/remove "contacts" x))

;;;; The resources
;; with shared pieces of documentation
(def name-param
  {:name "name", :dataType "string", :paramType "path", :required "true", :description "The name of the contact", :allowMultiple false})

(def body-param
  {:dataType "Contact", :paramType "body", :required true, :allowMultiple false})

(defresource contact-collection
  :desc "Operations with multiple contacts"
  :mixins [collection-resource]
  :clinks {:item ::contact-item}
  :data-key :contacts
  :exists? (fn [ctx] {:contacts (contacts-all)})
  :post! (fn [ctx] {:item (-> ctx :request :non-query-params contacts-insert!)})
  :count (fn [req] (contacts-count))
  :doc {:get {:nickname "getContacts", :summary "Get all contacts"}
        :post {:nickname "createContact", :summary "Create a contact"}})

(defresource contact-item
  :desc "Operations with individual contacts"
  :url "/{_id}"
  :mixins [item-resource]
  :clinks {:collection ::contact-collection}
  :data-key :contact
  :exists? (fn [ctx]
             (if-let [doc (-> ctx :request :route-params :_id contacts-find-by-id)]
               {:contact doc}))
  :put! (fn [ctx]
          (-> ctx :request :non-query-params (contacts-update! (:contact ctx)))
          {:contact (-> ctx :request :route-params :_id contacts-find-by-id)})
  :delete! (fn [ctx]
             (-> ctx :contact contacts-delete!)
             {:contact nil})
  :doc {:get {:nickname "getContact", :summary "Get a contact", :parameters [name-param]}
        :put {:nickname "updateContact", :summary "Overwrite a contact", :parameters [name-param body-param]}
        :delete {:nickname "deleteContact", :summary "Delete a contact", :parameters [name-param]}})

;;;; The group
(defgroup contact-group
  :url "/contacts"
  :add-to-resources {:schema contact-schema}  ; instead of typing the same for all resources in the group
  :resources [contact-collection contact-item])

;;;; The handler
(defroutes site
  :groups [contact-group]
  :documenters [schema-doc schema-root-doc swagger-doc swagger-root-doc])

(defn -main [] (run-server site {:port 8080}))

Also, API Documentation is available.

Contributing

By participating in this project you agree to follow the Contributor Code of Conduct.

Please take over the whole project!
I don't use Clojure a lot nowadays.
Talk to me: greg@unrelenting.technology.

License

This is free and unencumbered software released into the public domain.
For more information, please refer to the UNLICENSE file or unlicense.org.

About

[UNMAINTAINED] A hypermedia REST HTTP API library for Clojure

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published