Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from Motiva-AI/issue-2/replace-interceptor-impl…
…ementation Issue 2/replace interceptor implementation
- Loading branch information
Showing
6 changed files
with
92 additions
and
91 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
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 |
---|---|---|
@@ -1,77 +1,69 @@ | ||
(ns stepwise.interceptors.core | ||
(:refer-clojure :exclude [compile]) | ||
(:require [clojure.tools.logging :as log])) | ||
(:require [sieppari.core :as s])) | ||
|
||
; cribbed from re-frame -- thanks re-frame! | ||
(defn well-formed-interceptor-tuple? | ||
"Interceptor-tuple should be a tuple of the form, | ||
[:name {:enter (fn [ctx] ... (update ctx :request myfn1)) | ||
:leave (fn [ctx] ... (update ctx :response myfn2))}] | ||
(defn- invoke-interceptor-fn | ||
; TODO clean up names | ||
[context [id interceptor] direction] | ||
(log/trace (prn-str {:interceptor id | ||
:direction direction | ||
:context context})) | ||
(if-let [f (get interceptor direction)] | ||
(f context) | ||
context)) | ||
A few notes: | ||
(defn- invoke-interceptors | ||
([context direction] | ||
(loop [context context] | ||
(let [queue (:queue context)] ;; future interceptors | ||
(if (empty? queue) | ||
context | ||
(let [interceptor (peek queue) ;; next interceptor to call | ||
stack (:stack context)] ;; already completed interceptors | ||
(recur (-> context | ||
(assoc :queue (pop queue) | ||
:stack (conj stack interceptor)) | ||
(invoke-interceptor-fn interceptor direction))))))))) | ||
1. `:enter` and `:leave` functions need to return `ctx` or an updated | ||
version of `ctx` for the next interceptor in the chain | ||
2. either `:enter` or `:leave` fn can be nil if not used | ||
3. incoming data are tucked away in `:request` for `:enter` fn | ||
4. outgoing data are in `:response` for `:leave` fn. | ||
(defn- change-direction [context] | ||
(-> context | ||
(dissoc :queue) | ||
(assoc :queue (:stack context)))) | ||
Reference: | ||
https://github.com/metosin/sieppari" | ||
[interceptor-tuple] | ||
(let [[interceptor-name interceptor-map] interceptor-tuple] | ||
(and (vector? interceptor-tuple) | ||
(= (count interceptor-tuple) 2) | ||
(keyword? interceptor-name) | ||
(map? interceptor-map) | ||
(or (nil? (:enter interceptor-map)) | ||
(fn? (:enter interceptor-map))) | ||
(or (nil? (:leave interceptor-map)) | ||
(fn? (:leave interceptor-map)))))) | ||
|
||
(defn assert-named-chain | ||
[named-chain] | ||
(doseq [[index interceptor-tuple] (map vector | ||
(range 0 (count named-chain)) | ||
named-chain)] | ||
(when-not (well-formed-interceptor-tuple? interceptor-tuple) | ||
(throw (ex-info "Malformed interceptor-tuple. See (doc stepwise.interceptors.core/well-formed-interceptor-tuple?) for example." | ||
{:index index | ||
:form interceptor-tuple}))))) | ||
|
||
(defn- assoc-send-heartbeat-fn-to-context-interceptor | ||
[send-heartbeat-fn] | ||
{:enter (fn [ctx] (assoc ctx :send-heartbeat-fn send-heartbeat-fn))}) | ||
|
||
(defn well-formed-interceptor? | ||
"Interceptor should be a tuple of the form, | ||
[:name {:before (fn [env] ... env) | ||
:after (fn [env] ... env)}] | ||
(defn- interceptor-tuples->interceptors [interceptor-tuples] | ||
(map second interceptor-tuples)) | ||
|
||
:before and :after fn needs to return env or an updated version of env for | ||
the next interceptor in the queue. Either fn can be nil if not used." | ||
[interceptor] | ||
(let [stage-map (second interceptor)] | ||
(and (vector? interceptor) | ||
(= (count interceptor) 2) | ||
(keyword? (first interceptor)) | ||
(map? stage-map) | ||
(or (nil? (:before stage-map)) | ||
(fn? (:before stage-map))) | ||
(or (nil? (:after stage-map)) | ||
(fn? (:after stage-map)))))) | ||
(defn- prepend-this-interceptor-to-interceptor-chain [this-interceptor chain] | ||
(cons this-interceptor | ||
chain)) | ||
|
||
(defn- form-interceptor-chain [handler-fn interceptors] | ||
(concat interceptors [handler-fn])) | ||
|
||
(defn compile | ||
"Returns a fn that exercises a queue of interceptors against a task and returns a result. | ||
"Returns a fn that exercises a chain of interceptor-tuples against a task | ||
and returns a result. Uses metosin/siepppari under the hood." | ||
[named-chain handler-fn] | ||
(assert-named-chain named-chain) | ||
|
||
Reference: | ||
https://day8.github.io/re-frame/Interceptors/" | ||
[queue] | ||
(doseq [[index interceptor] (map vector | ||
(range 0 (count queue)) | ||
queue)] | ||
(when-not (well-formed-interceptor? interceptor) | ||
(throw (ex-info "Malformed interceptor" | ||
{:index index | ||
:form interceptor})))) | ||
(with-meta (fn execute [input send-heartbeat] | ||
(-> {:input input | ||
:output nil | ||
:context {:send-heartbeat send-heartbeat} | ||
:stack () | ||
:queue (into () (reverse queue))} | ||
(invoke-interceptors :before) | ||
change-direction | ||
(invoke-interceptors :after) | ||
:output)) | ||
(with-meta (fn [input send-heartbeat-fn] | ||
(s/execute | ||
(->> named-chain | ||
(interceptor-tuples->interceptors) | ||
(prepend-this-interceptor-to-interceptor-chain (assoc-send-heartbeat-fn-to-context-interceptor send-heartbeat-fn)) | ||
(form-interceptor-chain handler-fn)) | ||
input)) | ||
{:heartbeat? true})) | ||
|
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,16 +1,25 @@ | ||
(ns stepwise.interceptors.core-test | ||
(:refer-clojure :exclude [compile]) | ||
(:require [clojure.test :as test] | ||
(:require [clojure.test :refer [deftest testing is]] | ||
[stepwise.interceptors.core :as main])) | ||
|
||
(def hello-world | ||
[:hello-world {:before (fn [context] | ||
(assoc context :output :hello-world))}]) | ||
(def inc-x-interceptor-tuple | ||
[:inc-x | ||
{:enter (fn [ctx] (update-in ctx [:request :x] inc))}]) | ||
|
||
(defn handler [request] | ||
{:y (inc (:x request))}) | ||
|
||
(defn execute [queue task] | ||
((main/compile queue) task (fn []))) | ||
((main/compile queue handler) task (fn []))) | ||
|
||
(deftest compile-test | ||
(testing "interceptor with handler-fn" | ||
(is (= {:y 42} | ||
(execute [inc-x-interceptor-tuple] {:x 40})))) | ||
|
||
(test/deftest compile | ||
(test/is (= :hello-world | ||
(execute [hello-world] {})))) | ||
(testing "send-heartbeat-fn is associated to internal context" | ||
(execute [[:check-heartbeat-fn | ||
{:enter (fn [ctx] (is (fn? (:send-heartbeat-fn ctx))) ctx)}]] | ||
{:x 40}))) | ||
|
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