-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding async interceptor helpers (#1)
- Loading branch information
1 parent
03a732a
commit d45187b
Showing
10 changed files
with
274 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
## Index | ||
- Functions | ||
- [async-blocker-interceptor](#async-blocker-interceptor) | ||
- [async-fetch-output-interceptor](#async-fetch-output-interceptor) | ||
- [async-output-interceptor](#async-output-interceptor) | ||
|
||
## Functions | ||
|
||
- <h3><a id='async-blocker-interceptor'></a><span style="color:green">async-blocker-interceptor</span> [] [merge-data-fn]</h3><br> | ||
Async blocker interceptor, the one mandatory to resolve async-channels created by `async-fetch-output-interceptor` | ||
before jumping into the handlers. Optional merge-data-fn is the function used to merge different data returned by the interceptors. | ||
The default fn is `merge`. <br> | ||
<br> | ||
- merge-data-fn ^fn : optional merge function, when not sent, `merge` is used as default<br> | ||
- returns ^map context map <br> | ||
```clojure | ||
(async-blocker-interceptor) | ||
``` | ||
```clojure | ||
(async-blocker-interceptor my-merge-fn) | ||
``` | ||
|
||
- <h3><a id='async-fetch-output-interceptor'></a><span style="color:green">async-fetch-output-interceptor</span> [m]</h3><br> | ||
Creates an async interceptor that receives a `{:name string? :enter fn? :leave fn?}` as param.<br> | ||
It creates a channel and adds it the context `:async-channels` map with interceptor `:name` as key.<br> | ||
The optional `:enter` function is triggered and the result is executed async and put to the channel while the queue goes on.<br> | ||
The optional `:leave` function is triggered async, but no result is stored anywhere <br> | ||
<br> | ||
- m ^map : interceptor map `{:name string? :enter fn? :leave fn?}` one of enter or leave is mandatory, both are also accepted<br> | ||
- returns ^map context map <br> | ||
```clojure | ||
(async-fetch-output-interceptor {:name :async-i-name | ||
:enter (fn [context] | ||
;... my body sync or async to be paralellized | ||
)}) | ||
``` | ||
|
||
- <h3><a id='async-output-interceptor'></a><span style="color:green">async-output-interceptor</span> [m]</h3><br> | ||
Creates an async interceptor that receives a `{:name string? :enter fn?}` as param.<br> | ||
It executes the `:enter` and/or `:leave` function async and the queue goes on while it is executed by the async threads in parallel. <br> | ||
<br> | ||
- m ^map : interceptor map `{:name string? :enter fn? :leave fn?}` one of enter or leave is mandatory, both are also accepted<br> | ||
- returns ^map context map <br> | ||
```clojure | ||
(async-output-interceptor {:name :async-i-name | ||
:enter (fn [context] | ||
;... my body sync or async to be parallelized | ||
)}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
# pedestal-api-helper | ||
Documentation: | ||
|
||
- [interceptors](https://github.com/mtsbarbosa/pedestal-api-helper/tree/main/doc/interceptors.md) | ||
- [params-helper](https://github.com/mtsbarbosa/pedestal-api-helper/tree/main/doc/params_helper.md) | ||
- [validation](https://github.com/mtsbarbosa/pedestal-api-helper/tree/main/doc/validation.md) | ||
- [async-interceptors](async_interceptors.md) | ||
- [interceptors](interceptors.md) | ||
- [params-helper](params_helper.md) | ||
- [validation](validation.md) |
69 changes: 69 additions & 0 deletions
69
integration/clj/pedestal_api_helper/async_interceptors_i_test.clj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
(ns clj.pedestal-api-helper.async-interceptors-i-test | ||
(:require [clj.pedestal-api-helper.core-test :refer [service-map]] | ||
[clojure.edn :as edn] | ||
[clojure.test :refer :all] | ||
[io.pedestal.http :as http] | ||
[io.pedestal.test :refer [response-for]] | ||
[mockfn.macros :refer [verifying]] | ||
[mockfn.matchers :refer [exactly]] | ||
[pedestal-api-helper.async-interceptors :as async-i])) | ||
|
||
(defn get-foo | ||
[request] | ||
{:status 200 :body (-> request :async-data)}) | ||
|
||
(defn http-out | ||
[v] v) | ||
|
||
(def fetch-interceptors | ||
[(async-i/async-fetch-output-interceptor {:name :async-1 | ||
:enter (fn [_] (http-out 1) 1)}) | ||
(async-i/async-blocker-interceptor)]) | ||
|
||
(def output-enter-interceptors | ||
[(async-i/async-output-interceptor {:name :async-2 | ||
:enter (fn [_] (http-out 2) 2)})]) | ||
|
||
(def output-leave-interceptors | ||
[(async-i/async-output-interceptor {:name :async-3 | ||
:leave (fn [_] (http-out 3) 3)})]) | ||
|
||
(def all-interceptors | ||
(into [] (concat output-enter-interceptors fetch-interceptors output-leave-interceptors))) | ||
|
||
(def service | ||
(::http/service-fn (http/create-servlet (assoc service-map | ||
::http/routes #{["/foo" :get (conj fetch-interceptors | ||
`get-foo) :route-name :get-foo] | ||
["/foo-2" :get (conj output-enter-interceptors | ||
`get-foo) :route-name :get-foo-2] | ||
["/foo-3" :get (conj output-leave-interceptors | ||
`get-foo) :route-name :get-foo-3] | ||
["/foo-4" :get (conj all-interceptors | ||
`get-foo) :route-name :get-foo-4]})))) | ||
|
||
(deftest foo-get-test | ||
(verifying [(http-out 1) 1 (exactly 1)] | ||
(let [response (response-for service :get "/foo")] | ||
(is (= (:status response) 200)) | ||
(is (= (edn/read-string (:body response)) {:async-1 1}))))) | ||
|
||
(deftest foo-2-get-test | ||
(verifying [(http-out 2) 2 (exactly 1)] | ||
(let [response (response-for service :get "/foo-2")] | ||
(is (= (:status response) 200)) | ||
(is (= (:body response) ""))))) | ||
|
||
(deftest foo-3-get-test | ||
(verifying [(http-out 3) 3 (exactly 1)] | ||
(let [response (response-for service :get "/foo-3")] | ||
(is (= (:status response) 200)) | ||
(is (= (:body response) ""))))) | ||
|
||
(deftest foo-4-get-test | ||
(verifying [(http-out 2) 2 (exactly 1) | ||
(http-out 1) 1 (exactly 1) | ||
(http-out 3) 3 (exactly 1)] | ||
(let [response (response-for service :get "/foo-4")] | ||
(is (= (:status response) 200)) | ||
(is (= (edn/read-string (:body response)) {:async-1 1}))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
(ns clj.pedestal-api-helper.core-test | ||
(:require [clojure.test :refer :all] | ||
[io.pedestal.http :as http])) | ||
|
||
(def service-map {:env :test | ||
::http/routes #{} | ||
::http/resource-path "/public" | ||
|
||
::http/type :jetty | ||
::http/port 8080 | ||
::http/container-options {:h2c? true | ||
:h2? false | ||
:ssl? false}}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
(ns pedestal-api-helper.async-interceptors | ||
(:require [clojure.core.async :as async])) | ||
|
||
(defn- push-async-channel | ||
[context channel-id channel] | ||
(assoc-in context [:request :async-channels channel-id] channel)) | ||
|
||
(defn- push-async-data | ||
([context data] | ||
(assoc-in context [:request :async-data] (conj (get-in context [:request :async-data] []) | ||
data))) | ||
([context id data] | ||
(assoc-in context [:request :async-data id] data))) | ||
|
||
(defn- executes-context-async-fn | ||
[m-fn context] | ||
(async/go | ||
(do | ||
(println "exit executes-context-async-fn") | ||
(m-fn context))) | ||
context) | ||
|
||
(defn async-fetch-output-interceptor | ||
"Creates an async interceptor that receives a `{:name string? :enter fn? :leave fn?}` as param. | ||
It creates a channel and adds it the context `:async-channels` map with interceptor `:name` as key. | ||
The optional `:enter` function is triggered and the result is executed async and put to the channel while the queue goes on. | ||
The optional `:leave` function is triggered async, but no result is stored anywhere" | ||
[m] | ||
(let [enter-m (if (:enter m) (assoc m :enter (fn [context] | ||
(let [chan (async/chan)] | ||
(async/go | ||
(async/>! chan (get-in (push-async-data context (:name m) ((:enter m) context)) [:request :async-data]))) | ||
(push-async-channel context (:name m) chan)))) | ||
{}) | ||
leave-m (if (:leave m) (assoc m :leave #(executes-context-async-fn (:leave m) %)) | ||
{})] | ||
(merge enter-m leave-m))) | ||
|
||
(defn async-output-interceptor | ||
"Creates an async interceptor that receives a `{:name string? :enter fn?}` as param. | ||
It executes the `:enter` and/or `:leave` function async and the queue goes on while it is executed by the async threads in parallel." | ||
[m] | ||
(let [enter-m (if (:enter m) (assoc m :enter #(executes-context-async-fn (:enter m) %)) | ||
{}) | ||
leave-m (if (:leave m) (assoc m :leave #(executes-context-async-fn (:leave m) %)) | ||
{})] | ||
(merge enter-m leave-m))) | ||
|
||
(defn async-blocker-interceptor | ||
"Async blocker interceptor, the one mandatory to resolve async-channels created by all `async-fetch-output-interceptor` | ||
before jumping into the handlers. Optional merge-data-fn is the function used to merge different data returned by the interceptors. | ||
The default fn is `merge`" | ||
([merge-data-fn] | ||
{:name ::async-blocker | ||
:enter (fn [context] | ||
(println "async-blocker-interceptor") | ||
(let [channels (-> context :request :async-channels vals) | ||
merged-channels (async/map merge-data-fn channels) | ||
async-data (async/<!! merged-channels) | ||
context (assoc-in context [:request :async-data] async-data)] | ||
(async/close! merged-channels) | ||
context))}) | ||
([] | ||
(async-blocker-interceptor merge))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
(ns clj.pedestal-api-helper.async-interceptors-test | ||
(:require [clojure.core.async :as async] | ||
[clojure.test :refer :all] | ||
[pedestal-api-helper.async-interceptors :as a-int]) | ||
(:import [clojure.core.async.impl.channels ManyToManyChannel])) | ||
|
||
(deftest async-fetch-output-interceptor-test | ||
(testing "only enter and name are in the context" | ||
(let [async-i (a-int/async-fetch-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1)})] | ||
(is (= 2 (count (select-keys async-i [:name :enter :leave])))))) | ||
(testing "only leave and name are in the context" | ||
(let [async-i (a-int/async-fetch-output-interceptor {:name :async-1 | ||
:leave (fn [_] 1)})] | ||
(is (= 2 (count (select-keys async-i [:name :enter :leave])))))) | ||
(testing "enter, leave and name are in the context" | ||
(let [async-i (a-int/async-fetch-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1) | ||
:leave (fn [_] 1)})] | ||
(is (= 3 (count (select-keys async-i [:name :enter :leave])))))) | ||
(testing "async-channel is attached" | ||
(let [async-i (a-int/async-fetch-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1)}) | ||
context-executed ((:enter async-i) {})] | ||
(is (instance? ManyToManyChannel (-> context-executed :request :async-channels :async-1))) | ||
(is (= (-> context-executed :request :async-channels :async-1 async/<!! :async-1) | ||
1)))) | ||
(testing "async-channels are attached" | ||
(let [async-i (a-int/async-fetch-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1)}) | ||
async-i-2 (a-int/async-fetch-output-interceptor {:name :async-2 | ||
:enter (fn [_] 2)}) | ||
context-executed (-> {} | ||
((:enter async-i)) | ||
((:enter async-i-2)))] | ||
(is (instance? ManyToManyChannel (-> context-executed :request :async-channels :async-1))) | ||
(is (= (-> context-executed :request :async-channels :async-1 async/<!! :async-1) | ||
1)) | ||
(is (= (-> context-executed :request :async-channels :async-2 async/<!! :async-2) | ||
2))))) | ||
|
||
(deftest async-fetch-interceptor-test | ||
(testing "only enter and name are in the context" | ||
(let [async-i (a-int/async-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1)})] | ||
(is (= 2 (count (select-keys async-i [:name :enter :leave])))))) | ||
(testing "only leave and name are in the context" | ||
(let [async-i (a-int/async-output-interceptor {:name :async-1 | ||
:leave (fn [_] 1)})] | ||
(is (= 2 (count (select-keys async-i [:name :enter :leave])))))) | ||
(testing "enter, leave and name are in the context" | ||
(let [async-i (a-int/async-output-interceptor {:name :async-1 | ||
:enter (fn [_] 1) | ||
:leave (fn [_] 1)})] | ||
(is (= 3 (count (select-keys async-i [:name :enter :leave]))))))) |