started the rest api

This commit is contained in:
didier amyot committed 2018-06-16 17:24:30 -04:00
1 parent 516cccf4cd
commit ad924ca5c7
10 files changed
+320 -91

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
(ns book.cli
(:require [book.match :refer [sell?]]
[book.util :refer :all]
[book.exchange :refer [empty-book process-orders create-exchange-for-security]]
[book.order] ;; for spec side effect
[slingshot.slingshot :refer [try+ throw+]]
[clojure.edn :as edn]
[clojure.core.async :refer [chan go >!! <!!]]
[clojure.spec.alpha :as s]))
(def incoming-order-channel (chan))
;(def trades-channel (chan))
;
;(def new-book-order-channel (chan))
(defn print-chan [prefix c]
(loop [shit (<!! c)]
(println prefix shit)
(recur (<!! c))))
;(def master-book {:sell (create-sell-book)
; :buy (create-buy-book)})
;;; repl things.
(defn read-order []
(try
(print "$ ")
(let [order (edn/read-string (read-line))
valid? (s/valid? :book.order/order order)]
(if valid? order nil))
(catch Exception e nil)))
(def order-ids (atom -1))
(defn add-id-to-order [order]
(assoc order :book.order/id (swap! order-ids inc)))
(defn -main [& args]
(let [initial-book empty-book
incoming-order-channel (chan)
[trades-channel new-book-order-channel] (create-exchange-for-security initial-book incoming-order-channel)]
(go (print-chan "trade > " trades-channel))
(go (print-chan "order > " new-book-order-channel))
(loop [order (read-order)]
(if (some? order)
(do
(let [order-with-id (add-id-to-order order)]
(>!! incoming-order-channel order-with-id)
(println "\n\norder accepted with id: " (:book.order/id order-with-id))))
(println "invalid order"))
(recur (read-order)))))
+7 -77
View File
@@ -1,80 +1,10 @@
(ns book.core
(:require [book.match :refer [sell?
create-buy-book
create-sell-book
match-order-in-book]]
[book.util :refer :all]
[book.order] ;; for spec side effect
[clojure.edn :as edn]
[clojure.core.async :refer [chan go >!! <!!]]
[clojure.spec.alpha :as s])
(:require [book.cli]
[book.web])
(:gen-class))
(def incoming-order-channel (chan))
(def trades-channel (chan))
(def new-book-order-channel (chan))
(defn print-chan [prefix c]
(loop [shit (<!! c)]
(println prefix shit)
(recur (<!! c))))
(def master-book {:sell (create-sell-book)
:buy (create-buy-book)})
;
;(defn process-buy-order [order]
; (let [[x y x] (match-order-in-book @buy-book order)]))
;;; repl things.
(defn read-order []
(try
(print "$ ")
(let [order (edn/read-string (read-line))
valid? (s/valid? :book.order/order order)]
(if valid? order nil))
(catch Exception e nil)))
(def order-ids (atom -1))
(defn add-id-to-order [order]
(assoc order :book.order/id (swap! order-ids inc)))
(defn process-orders [master-book orders-channel trades-channel new-book-order-channel]
(loop [order (<!! orders-channel)
{sell-book :sell buy-book :buy} master-book]
(if (sell? order)
(do
(let [[trades new-buy-book leftover-order] (match-order-in-book buy-book order)
new-sell-book (if leftover-order (conj sell-book) leftover-order)]
(when (seq trades) (>!! trades-channel trades))
(when leftover-order (>!! new-book-order-channel leftover-order))
(recur (<!! orders-channel) {:sell new-sell-book :buy new-buy-book})))
(do
(let [[trades new-sell-book leftover-order] (match-order-in-book sell-book order)
new-buy-book (if leftover-order (conj buy-book) leftover-order)]
(when (seq trades) (>!! trades-channel trades))
(when leftover-order (>!! new-book-order-channel leftover-order))
(recur (<!! orders-channel) {:sell new-sell-book :buy new-buy-book}))))))
(defn -main [& args]
(println "enter orders in edn format.")
(go (print-chan "trade > " trades-channel))
(go (print-chan "order > " new-book-order-channel))
(go (process-orders master-book incoming-order-channel trades-channel new-book-order-channel))
(loop [order (read-order)]
(if (some? order)
(do
(let [order-with-id (add-id-to-order order)]
(>!! incoming-order-channel order-with-id)
(println "\n\norder accepted with id: " (:book.order/id order-with-id))))
(println "invalid order"))
(recur (read-order))))
(defn -main [& argv]
(case (first argv)
"web" (apply book.web/-main (rest argv))
"cli" (apply book.cli/-main (rest argv))
(println "First argument must be web or cli")))
+40
View File
@@ -0,0 +1,40 @@
(ns book.exchange
(:require [book.match :refer [sell?
create-buy-book
create-sell-book
match-order-in-book]]
[book.util :refer :all]
[clojure.core.async :refer [chan go >!! <!!]]))
(def empty-book {:sell (create-sell-book) :buy (create-buy-book)})
(defn process-abstract-sell [order reference-book same-type-book trades-channel new-book-order-channel]
(let [[trades new-reference-book leftover-order] (match-order-in-book reference-book order)
new-same-type-book (if leftover-order (conj same-type-book leftover-order) leftover-order)]
(when (seq trades) (doseq [trade trades] (>!! trades-channel trade)))
(when leftover-order (>!! new-book-order-channel leftover-order))
[new-reference-book new-same-type-book]))
(defn process-sell-order [order {sell-book :sell buy-book :buy} trades-channel new-book-order-channel]
(let [[new-buy-book new-sell-book] (process-abstract-sell order buy-book sell-book trades-channel new-book-order-channel)]
{:sell new-sell-book :buy new-buy-book}))
(defn process-buy-order [order {sell-book :sell buy-book :buy} trades-channel new-book-order-channel]
(let [[new-sell-book new-buy-book] (process-abstract-sell order sell-book buy-book trades-channel new-book-order-channel)]
{:sell new-sell-book :buy new-buy-book}))
(defn process-orders [master-book orders-channel trades-channel new-book-order-channel]
(loop [order (<!! orders-channel)
book master-book]
(let [process-order (if (sell? order) process-sell-order process-buy-order)
new-book (process-order order book trades-channel new-book-order-channel)]
(recur (<!! orders-channel) new-book))))
(defn create-exchange-for-security [initial-book incoming-order-channel]
(let [trades-channel (chan)
new-book-order-channel (chan)]
(go (process-orders initial-book incoming-order-channel trades-channel new-book-order-channel))
[trades-channel new-book-order-channel]))
+43
View File
@@ -0,0 +1,43 @@
(ns book.rest.util
(:require [cheshire.core :refer [generate-string]]
[clojure.string :refer [split starts-with? ends-with?]]))
(def mime-types
[["application/json" generate-string]
["application/edn" str]])
(defn create-mime-type-predicate [accept]
(let [[accept-start accept-end] (split accept #"/")]
(fn [mimetype]
(let [[start end] (split mimetype #"/")]
(and (or (= "*" accept-start) (= start accept-start))
(or (= "*" accept-end) (= end accept-end)))))))
(defn get-best-serializer [request]
(let [accept (get-in request [:headers "accept"] "*/*")
accept-content-types (conj (split accept #",\s*") "*/*")
xf (comp (map create-mime-type-predicate)
(map (fn [mime-pred] (filter #(mime-pred (first %)) mime-types)))
cat
(take 1))]
(first (sequence xf accept-content-types))))
(defn get-status [status request]
(if (nil? status)
(case (:request-method request)
:get 200
:post 201
200)
status))
(defn build-response [request {status :status body :body :as response}]
(let [[content-type serializer] (get-best-serializer request)]
{:body (serializer response)
:headers {"Content-Type" content-type}
:status (get-status status request)}))
+77
View File
@@ -0,0 +1,77 @@
(ns book.web
(:require [book.exchange :refer [empty-book]]
[org.httpkit.server :refer [run-server]]
[compojure.route :refer [not-found]]
[compojure.handler :refer [api]]
[compojure.core :refer :all]
[book.order] ;; for spec side effect
[book.rest.util :refer [build-response]]
[clojure.spec.alpha :as s]
[slingshot.slingshot :refer [try+ throw+]]
[cheshire.core :refer [generate-string]]
[clojure.edn :as edn]))
(def order-id (atom -1))
(defn api-description [req]
(let [body {"/api/1/securities" "list all securities."
"/api/1/book" "For all information about the book."}]
(build-response req body)))
;; the api should look like...
;; trades ...
;; /:sec-id/trades/:trade-id
;; book ...
;; /:sec-id/book/:order
;;
(defn securities [books]
(fn [request]
(build-response request {:securities (keys books)})))
(defn get-security [books]
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]))
(defn validate-order [asked-order]
(if (not (s/valid? :book.order/order asked-order))
(throw+ {:status :4XX :headers {"Content-Type" "application/json"} :body "foo"}))
(assoc asked-order :book.order/id (swap! order-id inc)))
(defn get-order [body]
(try+
(-> body clojure.java.io/reader slurp edn/read-string)
(catch Exception e
(throw+ {:error-type :4XX :status 400 :body {:error "Invalid body format. Body must be edn format."}}))))
(defn create-order [books]
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
(try+
(let [asked-order (get-order body)
order (validate-order asked-order)
sec-id (keyword sec-id-str)
book (get books sec-id)]
(build-response request {:order order :book book}))
(catch [:error-type :4XX] e (build-response request e))
(catch [:error-type :5XX] e (build-response request e)))))
(defn create-routes [books]
(routes
(context "/api/1" []
(GET "/" [] api-description)
(GET "/securities" [] (securities books))
(GET "/securities/:sec-id" [] (get-security books))
(POST "/securities/:sec-id" [] (create-order books)))
(not-found "not found")))
(defn -main [& args]
(let [books {:foo empty-book :bar empty-book}
api-routes (create-routes books)]
(run-server (api api-routes) {:port 8080})))