Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added project.clj #1 #3

Merged
merged 3 commits into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: 2

jobs:
build:
working_directory: ~/rop
docker:
- image: circleci/clojure:lein-2.8.1
environment:
LEIN_ROOT: nbd
LC_ALL: C
steps:
- checkout
- restore_cache:
key: rop-{{ checksum "project.clj" }}
- run: lein deps
- save_cache:
paths:
- ~/.m2
- ~/.lein
key: rop-{{ checksum "project.clj" }}
- run: lein test
- run: lein cloverage --fail-threshold 95
- run: lein kibit
- run: lein eastwood
12 changes: 12 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(defproject tol "0.0.0"
:description "Yet another Railway Oriented Programming in Clojure"
:url "https://github.com/druids/rop"
:license {:name "MIT License"
:url "http://opensource.org/licenses/MIT"}

:dependencies [[org.clojure/clojure "1.8.0"]
[funcool/cats "2.1.0"]]

:profiles {:dev {:plugins [[lein-cloverage "1.0.10"]
[lein-kibit "0.1.6"]
[jonase/eastwood "0.2.5"]]}})
62 changes: 62 additions & 0 deletions src/rop/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
(ns rop.core
"Implementation of Railway Oriented Programming based on
https://gist.github.com/ah45/7518292c620679c460557a7038751d6d"
(:require
[cats.builtin]
[cats.core :as m]
[cats.monad.either :as meither]))


(def succeed
"Convert a value into a two-track (success) result"
meither/right)


(def fail
"Convert a value into a two-track (failure) result"
meither/left)


(def success?
"Returns true if the given two-track value is a success"
meither/right?)


(def failure?
"Returns true if the given two-track value is a failure"
meither/left?)


(defn switch
"Converts a normal fn into a switch (one-track input, two-track output)"
[f]
(comp succeed f))


(defn tee
"Returns a fn that calls f on its argument and returns its argument.
Converts otherwise 'dead-end' fns into one-track fns."
[f]
(fn [v]
(f v)
v))


(def dead
"A shortcut for calling (rop/switch (rop/tee send-email!)"
(comp switch tee))


(defn >>=
"An infix version of bind for piping two-track values into switch fns. Can be used to pipe two-track values
through a series of switch fns. A result of this function should be Ring's response.
First parameter is a success key (it will be used as :body in result hash-map). Second is an input hash-map
it will be passed throgh switch fns. And rest parameters as switch fns."
[success-key input & fns]
(let [result (apply m/>>= (into [(succeed input)] fns))
extracted-result (m/extract result)]
(if (success? result)
{:body (get extracted-result success-key)
:status (get-in extracted-result [:response :status] 200)
:headers (get-in extracted-result [:response :headers] {})}
extracted-result)))
58 changes: 58 additions & 0 deletions test/rop/core_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
(ns rop.core-test
(:require
[clojure.string :refer [blank? lower-case]]
[clojure.test :refer [deftest is testing]]
[rop.core :as rop]))


(defn- format-email
[input]
(update input :email lower-case))


(defn- validate-email
[input]
(if (-> input :email blank?)
(rop/fail {:status 400, :body {:errors {:email ["Invalid format"]}}})
(rop/succeed input)))


(defn- create-user
[input]
(-> input
(assoc :new-user {:email (:email input), :id 1})
(assoc-in [:response :status] 201)
(assoc-in [:response :headers] {:content-type :application/json})))


(defn- send-email!
[input]
;; send e-mail here
(println "Sending e-mail"))


(defn try-to-create-user
[input]
(rop/>>= :new-user
input
(rop/switch format-email)
validate-email
(rop/switch create-user)
(rop/dead send-email!)))


(deftest rop-test
(testing "Should return a success Ring's response with headers and status"
(is (= {:body {:email "foo@bar.com", :id 1}, :status 201, :headers {:content-type :application/json}}
(try-to-create-user {:email "FOO@BAR.COM", :new-user nil}))))

(testing "Should return a failure Ring's response"
(is (= {:body {:errors {:email ["Invalid format"]}} :status 400}
(try-to-create-user {:email "", :new-user nil}))))

(testing "Should return a success Ring's response"
(is (= {:body "foo@bar.com", :status 200, :headers {}}
(rop/>>= :email
{:email "FOO@BAR.COM"}
(rop/switch format-email)
validate-email)))))