From ad924ca5c7269c0f829b177ff6be8678546a931d Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sat, 16 Jun 2018 17:24:30 -0400 Subject: [PATCH] started the rest api --- book.iml | 17 ++++++++ project.clj | 10 ++++- src/book/cli.clj | 56 ++++++++++++++++++++++++ src/book/core.clj | 84 +++--------------------------------- src/book/exchange.clj | 40 +++++++++++++++++ src/book/rest/util.clj | 43 ++++++++++++++++++ src/book/web.clj | 77 +++++++++++++++++++++++++++++++++ test/book/core_test.clj | 13 ------ test/book/exchange_test.clj | 28 ++++++++++++ test/book/rest/util_test.clj | 43 ++++++++++++++++++ 10 files changed, 320 insertions(+), 91 deletions(-) create mode 100644 src/book/cli.clj create mode 100644 src/book/exchange.clj create mode 100644 src/book/rest/util.clj create mode 100644 src/book/web.clj delete mode 100644 test/book/core_test.clj create mode 100644 test/book/exchange_test.clj create mode 100644 test/book/rest/util_test.clj diff --git a/book.iml b/book.iml index 3eac619..e8353b5 100644 --- a/book.iml +++ b/book.iml @@ -15,19 +15,33 @@ + + + + + + + + + + + + + + @@ -53,7 +67,10 @@ + + + \ No newline at end of file diff --git a/project.clj b/project.clj index 9081c7b..c6b950c 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,15 @@ :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.9.0"] - [org.clojure/core.async "0.4.474"]] + [org.clojure/core.async "0.4.474"] + ;; web + [http-kit "2.3.0"] + [compojure "1.6.1"] + [javax.servlet/javax.servlet-api "3.1.0"] + + ;;utils + [slingshot "0.12.2"] + [cheshire "5.8.0"]] :java-source-paths ["src/java"] :main ^:skip-aot book.core :target-path "target/%s" diff --git a/src/book/cli.clj b/src/book/cli.clj new file mode 100644 index 0000000..4a2147f --- /dev/null +++ b/src/book/cli.clj @@ -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 >!! " 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))))) diff --git a/src/book/core.clj b/src/book/core.clj index 2090939..38830ed 100644 --- a/src/book/core.clj +++ b/src/book/core.clj @@ -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 >!! !! trades-channel trades)) - (when leftover-order (>!! new-book-order-channel leftover-order)) - (recur (!! trades-channel trades)) - (when leftover-order (>!! new-book-order-channel leftover-order)) - (recur ( " 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"))) diff --git a/src/book/exchange.clj b/src/book/exchange.clj new file mode 100644 index 0000000..77dbdcb --- /dev/null +++ b/src/book/exchange.clj @@ -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 >!! !! 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 ( 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}))) \ No newline at end of file diff --git a/test/book/core_test.clj b/test/book/core_test.clj deleted file mode 100644 index 3e35b1a..0000000 --- a/test/book/core_test.clj +++ /dev/null @@ -1,13 +0,0 @@ -(ns book.core-test - (:require [midje.sweet :refer :all])) - -(facts "big fact" - (fact "about trivialities" - (let [expt 3] - (inc 2) => expt - (inc 2) => expt)) - - (fact "about trivialities 2" - (let [expt 3] - (inc 2) => expt - (inc 2) => expt))) diff --git a/test/book/exchange_test.clj b/test/book/exchange_test.clj new file mode 100644 index 0000000..389620c --- /dev/null +++ b/test/book/exchange_test.clj @@ -0,0 +1,28 @@ +(ns book.exchange-test + (:require [midje.sweet :refer :all] + [book.exchange :refer :all] + [book.match :refer [create-buy-book + create-sell-book]] + [clojure.core.async :refer [chan go >!! !! incoming-order-channel {:type :sell :quantity 10 :price 2}) + ( {:type :sell :quantity 10 :price 2} + (>!! incoming-order-channel {:type :buy :quantity 20 :price 2}) + ( #:book.order{:book-id nil + :order-id nil + :price 2 + :quantity 10 + :type :sell} + ( {:type :buy :quantity 10 :price 2}))) + + + + + diff --git a/test/book/rest/util_test.clj b/test/book/rest/util_test.clj new file mode 100644 index 0000000..42ace8c --- /dev/null +++ b/test/book/rest/util_test.clj @@ -0,0 +1,43 @@ +(ns book.rest.util-test + (:require [book.rest.util :refer [build-response]] + [midje.sweet :refer :all] + [clojure.test :refer :all])) + +(defn expt-response [body content-type status] + {:body body + :headers {"Content-Type" content-type} + :status status}) + +(facts "the building the response" + (fact "building the response in edn" + (let [content-type "application/edn" + request {:headers {"accept" content-type}} + response "foo" + expt (expt-response "foo" "application/edn" 200)] + (build-response request response) => expt)) + (fact "building the response in edn" + (let [content-type "application/edn" + request {:headers {"accept" content-type}} + response {:foo [1 2 3]} + expt (expt-response "{:foo [1 2 3]}" "application/edn" 200)] + (build-response request response) => expt)) + (fact "building the response in edn" + (let [content-type "application/edn,application/json" + request {:headers {"accept" content-type}} + response {:foo [1 2 3]} + expt (expt-response "{:foo [1 2 3]}" "application/edn" 200)] + (build-response request response) => expt)) + (fact "building the response in json" + (let [content-type "text/html,application/xhtml+xml,application/json,application/xml;q=0.9,*/*;q=0.8" + request {:headers {"accept" content-type}} + response {:foo [1 2 3]} + expt (expt-response "{\"foo\":[1,2,3]}" "application/json" 200)] + (build-response request response) => expt)) + (fact "My browser should receive json" + (let [content-type "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + request {:headers {"accept" content-type}} + response {:foo [1 2 3]} + expt (expt-response "{\"foo\":[1,2,3]}" "application/json" 200)] + (build-response request response) => expt))) + +