├── .gitignore
├── Makefile
├── src
└── pact
│ ├── manifold.clj
│ ├── comp_future.clj
│ ├── core_async.cljc
│ └── core.cljc
├── trash.clj
├── UNLICENSE
├── project.clj
├── README.md
└── test
└── pact
└── core_test.cljc
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | profiles.clj
5 | pom.xml
6 | pom.xml.asc
7 | *.jar
8 | *.class
9 | /.lein-*
10 | /.nrepl-port
11 | /.prepl-port
12 | .hgignore
13 | .hg/
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | .PHONY: test-all
3 | test-all: test test-js
4 |
5 | .PHONY: test
6 | test:
7 | lein test
8 |
9 |
10 | .PHONY: release
11 | release:
12 | lein release
13 |
14 |
15 | test-js:
16 | lein with-profile +cljs cljsbuild once
17 | node target/tests.js
18 |
--------------------------------------------------------------------------------
/src/pact/manifold.clj:
--------------------------------------------------------------------------------
1 | (ns pact.manifold
2 | (:require
3 | [pact.core :as p]
4 | [manifold.deferred :as d]))
5 |
6 |
7 | (extend-protocol p/IPact
8 |
9 | manifold.deferred.IDeferred
10 |
11 | (-then [this func]
12 | (d/chain this func))
13 |
14 | (-error [this func]
15 | (d/catch this func)))
16 |
--------------------------------------------------------------------------------
/src/pact/comp_future.clj:
--------------------------------------------------------------------------------
1 | (ns pact.comp-future
2 | (:refer-clojure :exclude [future])
3 | (:require
4 | [pact.core :as p])
5 | (:import
6 | java.util.function.Function
7 | java.util.function.Supplier
8 | java.util.concurrent.CompletableFuture))
9 |
10 |
11 | (extend-protocol p/IPact
12 |
13 | CompletableFuture
14 |
15 | (-then [this func]
16 | (.thenApply this (reify Function
17 | (apply [_ x]
18 | (func x)))))
19 |
20 | (-error [this func]
21 | (.exceptionally this (reify Function
22 | (apply [_ e]
23 | (func (ex-cause e)))))))
24 |
25 |
26 | (defmacro future [& body]
27 | `(CompletableFuture/supplyAsync (reify Supplier
28 | (get [_]
29 | ~@body))))
30 |
--------------------------------------------------------------------------------
/trash.clj:
--------------------------------------------------------------------------------
1 | (-then [c-in func]
2 | (let [c-out (a/chan 1)]
3 | (a/take! c-in
4 | (fn [x]
5 | (a/put! c-out
6 | (if (exception? x)
7 | x
8 | (try
9 | (func x)
10 | (catch Exception e
11 | e))))))
12 | c-out))
13 |
14 |
15 | (-error [c-in func]
16 | (let [c-out (a/chan 1)]
17 | (a/take! c-in
18 | (fn [x]
19 | (a/put! c-out
20 | (if (exception? x)
21 | (try
22 | (func x)
23 | (catch Exception e
24 | e))
25 | x))))
26 | c-out))
27 |
28 |
29 | (a/take! ch (fn [x]
30 | (is (= 1 x))) )
31 |
--------------------------------------------------------------------------------
/src/pact/core_async.cljc:
--------------------------------------------------------------------------------
1 | (ns pact.core-async
2 | (:require
3 | [pact.core :as p]
4 | #?(:clj [clojure.core.async :as a])
5 | #?(:cljs [cljs.core.async :as a])))
6 |
7 |
8 | (defn throwable? [e]
9 | (instance? #?(:clj Throwable :cljs js/Error) e))
10 |
11 |
12 | (extend-protocol p/IPact
13 |
14 | #?(:clj clojure.core.async.impl.channels.ManyToManyChannel
15 | :cljs cljs.core.async.impl.channels.ManyToManyChannel)
16 |
17 | (-then [this func]
18 | (let [out
19 | (a/promise-chan (map (fn [x]
20 | (if (throwable? x)
21 | x
22 | (func x))))
23 | identity)]
24 |
25 | (a/take! this (fn [x]
26 | (a/put! out x)) )
27 |
28 | out))
29 |
30 | (-error [this func]
31 | (let [out
32 | (a/promise-chan (map (fn [x]
33 | (if (throwable? x)
34 | (func x)
35 | x)))
36 | identity)]
37 |
38 | (a/take! this (fn [x]
39 | (a/put! out x)) )
40 |
41 | out)))
42 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
in 49 | (then [x] 50 | (+ 1 x)) 51 | (then [x] 52 | (+ 1 x)) 53 | (then [x] 54 | (str "+" x "+")))] 55 | 56 | (a/go 57 | (a/>! in 1) 58 | (is (= "+3+" (a/ in 68 | (then [x] 69 | (throw (ex-info "Divide by zero" {}))) 70 | (then [x] 71 | 42) 72 | (error [e] 73 | (ex-message e)) 74 | (then [message] 75 | (str "<<< " message " >>>")))] 76 | 77 | (a/go 78 | (a/>! in 1) 79 | (is (= "<<< Divide by zero >>>" (a/ in 89 | (then [x] 90 | (throw (new js/Error "err 1"))) 91 | (then [x] 92 | 42) 93 | (error [e] 94 | (throw (new js/Error "err 2"))) 95 | (error [e] 96 | (ex-message e)) 97 | (then [message] 98 | (str "<<< " message " >>>")))] 99 | 100 | (a/go 101 | (a/>! in 1) 102 | (is (= "<<< err 2 >>>" (a/ in 115 | (then [x] 116 | (+ 1 x)) 117 | (then [x] 118 | (+ 1 x)) 119 | (then [x] 120 | (str "+" x "+")))] 121 | 122 | (a/>!! in 1) 123 | (is (= "+3+" (a/ in 130 | (then [x] 131 | (/ x 0)) 132 | (then [x] 133 | 42) 134 | (error [e] 135 | (ex-message e)) 136 | (then [message] 137 | (str "<<< " message " >>>")))] 138 | 139 | (a/put! in 1) 140 | (is (= "<<< Divide by zero >>>" (a/ in 147 | (then [x] 148 | (/ x 0)) 149 | (then [x] 150 | 42) 151 | (error [e] 152 | (+ 1 (ex-message e))) 153 | (error [e] 154 | (ex-message e)) 155 | (then [message] 156 | (str "<<< " message " >>>")))] 157 | 158 | (a/put! in 1) 159 | (is (str/starts-with? (a/ (new js/Date 1000000000000) 172 | (then [date] 173 | (.toGMTString date))))))) 174 | 175 | (testing "test nil" 176 | 177 | (is (= "" 178 | (-> nil 179 | (error-fn str) 180 | (then-fn str))))) 181 | 182 | (testing "boolean" 183 | 184 | (is (= "false" 185 | (-> true 186 | (then-fn not) 187 | (then-fn str))))) 188 | 189 | (testing "string" 190 | 191 | (is (= "42/hello" 192 | (-> 42 193 | (then [x] 194 | (-> x int str)) 195 | (then [x] 196 | (str x "/hello")))))) 197 | 198 | (is (= "Divide by zero" 199 | (-> 42 200 | (then [x] 201 | (throw (ex-info "Divide by zero" {}))) 202 | (then [x] 203 | (+ x 1000)) 204 | (error [e] 205 | (ex-message e))))) 206 | 207 | #?(:clj 208 | (testing "future" 209 | 210 | (is (= 103 211 | (-> (future (+ 1 2)) 212 | (then [x] 213 | (+ x 100))))))) 214 | 215 | (testing "error in error handler" 216 | 217 | (is (= "class java.lang.String cannot be cast to class java.lang.Number" 218 | (-> 42 219 | (then [x] 220 | (throw (ex-info "Divide by zero" {}))) 221 | (error [e] 222 | (throw (ex-info "class java.lang.String cannot be cast to class java.lang.Number" {}))) 223 | (error [e] 224 | (ex-message e)) 225 | (then [message] 226 | (subs message 0 63)))))) 227 | 228 | (testing "stop on error" 229 | 230 | (let [e 231 | (-> 0 232 | inc 233 | inc 234 | (then [x] 235 | (throw (ex-info "Divide by zero" {}))) 236 | (then [x] 237 | (inc x)) 238 | (then [x] 239 | (throw (ex-info "foobar" {}))) 240 | (then [x] 241 | (inc x)) 242 | (then [x] 243 | (inc x)) 244 | (then [x] 245 | (inc x)))] 246 | 247 | (is (= "Divide by zero" (ex-message e)))))) 248 | 249 | 250 | (deftest test-failure-ok 251 | 252 | (let [res 253 | (-> 1 254 | (then [x] 255 | (inc x)) 256 | (then [x] 257 | (failure {:foo 42})) 258 | (error [e] 259 | (ex-data e)))] 260 | 261 | (is (= {:foo 42 :ex/type :pact.core/failure} 262 | res)))) 263 | 264 | 265 | (deftest test-mapping-ok 266 | 267 | (is (= {:a 1 :b 2 :c 3 :d 6} 268 | 269 | (-> {:a 1 :b 2} 270 | 271 | (then [{:as scope :keys [a b]}] 272 | (assoc scope :c (+ a b))) 273 | 274 | (then [{:as scope :keys [a b c]}] 275 | (assoc scope :d (+ a b c))))))) 276 | 277 | 278 | #?(:clj 279 | 280 | (deftest test-comp-future-ok 281 | 282 | (testing "simple" 283 | 284 | (let [fut 285 | (future/future 1) 286 | 287 | res 288 | (-> fut 289 | (then [x] 290 | (inc x)))] 291 | 292 | (is (= 2 @res)))) 293 | 294 | (testing "ex type for comp future" 295 | 296 | (let [fut 297 | (future/future 1) 298 | 299 | res 300 | (-> fut 301 | (then [x] 302 | (inc x)) 303 | (then [x] 304 | (/ 0 0)) 305 | (error [e] 306 | e))] 307 | 308 | (is (= java.lang.ArithmeticException 309 | (-> res deref class))) 310 | 311 | (is (= "Divide by zero" 312 | (-> res deref ex-message)))) 313 | 314 | (testing "recover" 315 | 316 | (let [fut 317 | (future/future 1) 318 | 319 | res 320 | (-> fut 321 | (then [x] 322 | (inc x)) 323 | (then [x] 324 | (/ 0 0)) 325 | (error [e] 326 | (ex-message e)) 327 | (then [message] 328 | (str "<<< " message " >>>")))] 329 | 330 | (is (= "<<< Divide by zero >>>" 331 | @res)))) 332 | 333 | (testing "error in error" 334 | 335 | (let [fut 336 | (future/future 1) 337 | 338 | res 339 | (-> fut 340 | (then [x] 341 | (inc x)) 342 | (then [x] 343 | (/ 0 0)) 344 | (error [e] 345 | (+ 1 (ex-message e))) 346 | (error [e] 347 | (ex-message e)) 348 | (then [message] 349 | (str "<<< " message " >>>")))] 350 | 351 | (is (str/starts-with? 352 | @res "<<< class java.lang.String cannot be cast to class java.lang.Number"))))))) 353 | 354 | 355 | #?(:clj 356 | 357 | (deftest test-manifold-ok 358 | 359 | (testing "simple" 360 | 361 | (let [res 362 | (-> (d/future 1) 363 | (then [x] 364 | (inc x)) 365 | (then-fn inc))] 366 | 367 | (d/deferred? res) 368 | 369 | (is (= 3 @res)))) 370 | 371 | (testing "recovery" 372 | 373 | (let [res 374 | (-> (d/future 1) 375 | (then [x] 376 | (/ x 0)) 377 | (error [e] 378 | (ex-message e)))] 379 | 380 | (d/deferred? res) 381 | 382 | (is (= "Divide by zero" @res)))) 383 | 384 | (testing "error in error" 385 | 386 | (let [res 387 | (-> (d/future 1) 388 | (then [x] 389 | (/ x 0)) 390 | (error [e] 391 | (+ 1 (ex-message e))) 392 | (error [e] 393 | (ex-message e)))] 394 | 395 | (d/deferred? res) 396 | 397 | (is (str/starts-with? 398 | @res "class java.lang.String cannot be cast to class java.lang.Number")))))) 399 | 400 | 401 | 402 | #?(:cljs 403 | 404 | (do 405 | 406 | (deftest test-promise-ok 407 | 408 | (async done 409 | 410 | (let [p 411 | (-> (js/Promise.resolve 1) 412 | (then-fn inc) 413 | (then [x] 414 | (str "<<< " x " >>>")))] 415 | 416 | (a/go 417 | (is (= "<<< 2 >>>" (
(js/Promise.resolve 1) 427 | (then [x] 428 | (throw (ex-info "error" {}))) 429 | (error [e] 430 | (ex-message e)))] 431 | 432 | (a/go 433 | (is (= "error" (
(js/Promise.resolve 1) 443 | (then [x] 444 | (throw (ex-info "error1" {}))) 445 | (error [e] 446 | (throw (ex-info "error2" {}))) 447 | (error [e] 448 | (ex-message e)))] 449 | 450 | (a/go 451 | (is (= "error2" (