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

Implemented injector step #34

Merged
merged 4 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- Resolve qualified keywords to step functions. [#32](https://github.com/elasticpath/fonda/pull/32)
- Make the name key for step optional and possibly a keyword. [#33](https://github.com/elasticpath/fonda/pull/33)
- Injector steps that dynamically add steps [#34](https://github.com/elasticpath/fonda/pull/34)

### Removed

Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ The following section describes the parameters `fonda/execute` accepts.
| `:anomaly?` | Yes | A boolean or a function that gets a map and determines if it is an anomaly. |
| `:initial-ctx` | Yes | The data that initializes the context. Must be a map, `{}` by default. |

- **steps** - each item must be either a `Tap` or a `Processor`
- **steps** - each item must be either a `Tap` or a `Processor`, or a `Injector`

- tap

Expand All @@ -87,6 +87,14 @@ The following section describes the parameters `fonda/execute` accepts.
| `:processor` | No | A function that gets the context and returns data. The data is [assoced-in](https://clojuredocs.org/clojure.core/assoc-in) at the given path Can be asynchronous. If asynchronous it will still block the pipeline and interrupt the execution whenever either an anomaly or an exception happen. |
| `:path` | No | Path where to assoc the result of the processor |
| `:name` | Yes | The name of the step as string or keyword |
| `:name` | Yes | The name of the step |

- injector
| Key | Optional? | Notes |
|---|---|---|
| `:injector` | No | A function that gets the context and returns either a step or a collection of them. The step(s) returned will be executed right after the injector step and just before the next steps. Can be asynchronous.
| `:name` | Yes | The name of the injector step |


- **on-exception** Function called with an exception when any of the steps throws one.
- **on-success** Function called with the context if all steps succeed.
Expand All @@ -97,7 +105,7 @@ The following section describes the parameters `fonda/execute` accepts.

```clojure
(fonda/execute
{:initial-ctx {:env-var-xyz "value"}
{:initial-ctx {:env-var-xyz "value", :remote-thing-params {:p1 "p1" :p2 "p2"} :other-remote-thing-responses []}
arichiardi marked this conversation as resolved.
Show resolved Hide resolved

[{:processor (fn [ctx]
(ajax/GET "http://remote-thing-url.com" {:params (:remote-thing-params ctx)})
Expand All @@ -106,8 +114,15 @@ The following section describes the parameters `fonda/execute` accepts.
{:tap (fn [{:keys [remote-thing-response]}]
(println "the remote thing response was:" remote-thing-response))}

{:processor process-remote-thing-response ;; Pure function - ctx in - ctx out
:path [:remote-thing]}]
{:processor process-remote-thing-response ;; Pure function - ctx in - processed response out
:path [:remote-thing]}

;; Injector returns a collection of steps to be added right after the injector step
{:inject (fn [{:keys [remote-thing]}]
(->> (:side-effect-post-urls remote-thing)
(map (fn [side-effect-post-url]
{:tap (fn [{:keys [remote-thing-params]}]
(ajax/POST side-effect-post-url remote-thing-params))}))))}]

;; on-exception
(fn [exception]
Expand Down
8 changes: 6 additions & 2 deletions src/fonda/core.cljs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
(ns fonda.core
(:require [clojure.spec.alpha :as s]
[fonda.execute :as e]
(:require [fonda.execute :as e]
[fonda.step :as st]))

;;;;;;;;;;;;;;;;;
Expand All @@ -27,6 +26,11 @@
- path: Path where to assoc the result of the processor
- name: The name of the step

Injector:
- inject: A function that gets the context and returns either a step or a collection of them.
The step(s) returned will be executed right after the injector step and just before the next steps. Can be asynchronous.
- name: The name of the injector step

- on-success Callback that gets called with the context if all the steps succeeded.
- on-exception Callback that gets called with an exception when any step triggers one.
- [opt] on-anomaly Callback that gets called with an anomaly when any step returns one."
Expand Down
6 changes: 1 addition & 5 deletions src/fonda/core/specs.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
(s/def ::on-exception fn?)
(s/def ::on-anomaly (s/nilable fn?))

(s/def ::step
(s/or :tap-step ::step/tap-step
:processor-step ::step/processor-step))

(s/def ::steps (s/coll-of ::step))
(s/def ::steps (s/coll-of ::step/step))

(s/def ::config
(s/keys :opt-un [::anomaly? ::initial-ctx]))
Expand Down
18 changes: 13 additions & 5 deletions src/fonda/execute.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,26 @@
(assoc fonda-ctx :anomaly res)
(assoc-in fonda-ctx (concat [:ctx] path) res)))

(defn assoc-injector-result
[{:as fonda-ctx :keys [queue]} res]
(let [steps (if (sequential? res) res [res])]
(assoc fonda-ctx :queue (into #queue [] st/xf (concat steps queue)))))

(defn- try-step
"Tries running the given step (a tap step, or a processor step).
If an exception gets triggerd, an exception is added on the context.
If an anomaly is returned, an anomaly is added to the context"
[{:as fonda-ctx :keys [ctx]}
{:as step :keys [processor tap]}]
{:as step :keys [processor tap inject]}]
(try
(let [res (if processor (processor ctx) (tap ctx))
(let [res (cond
processor (processor ctx)
tap (tap ctx)
inject (inject ctx))
assoc-result-fn (cond
(not (nil? tap)) (partial assoc-tap-result fonda-ctx)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These (not (nil? thing)) can be safely translated to thing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right

(not (nil? processor)) (partial assoc-processor-result fonda-ctx (:path step)))]

(not (nil? processor)) (partial assoc-processor-result fonda-ctx (:path step))
(not (nil? inject)) (partial assoc-injector-result fonda-ctx))]
(if (a/async? res)
(a/continue res assoc-result-fn #(assoc fonda-ctx :exception %))
(assoc-result-fn res)))
Expand Down Expand Up @@ -133,4 +141,4 @@
{:anomaly-fn anomaly-fn})
{:ctx (or (:initial-ctx config) {})
:queue (into #queue [] st/xf steps)
:stack []}))))
:stack []}))))
5 changes: 5 additions & 0 deletions src/fonda/execute/specs.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@
(s/fdef assoc-tap-result
:args (s/cat :fonda-ctx ::fonda-context
:res any?))

(s/fdef assoc-injector-result
:args (s/cat :fonda-ctx ::fonda-context
:res (s/or :step ::step/step
:step-coll (s/coll-of ::step/step))))
16 changes: 12 additions & 4 deletions src/fonda/step.cljs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(ns fonda.step
(:require [clojure.spec.alpha :as s]
[fonda.meta :as meta]))
(:require [fonda.meta :as meta]))


(defrecord Tap
[;; A function that gets the context but doesn't augment it
Expand All @@ -19,17 +19,25 @@
;; Path were to attach the processor result on the context
path])

(defrecord Injector
[;; Function that returns step(s) to be injected right after this step on the queue
inject

;; Name for the step
name])

(defn resolve-function
[fn-or-keyword]
(if (qualified-keyword? fn-or-keyword)
(meta/kw->fn fn-or-keyword)
fn-or-keyword))

(defn step->record
[{:keys [tap processor] :as step}]
[{:keys [tap processor inject] :as step}]
(cond
tap (map->Tap (update step :tap resolve-function))
processor (map->Processor (update step :processor resolve-function))))
processor (map->Processor (update step :processor resolve-function))
inject (map->Injector (update step :inject resolve-function))))

(def ^{:doc "Step transducer."}
xf
Expand Down
10 changes: 10 additions & 0 deletions src/fonda/step/specs.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@
(s/def ::processor (s/or :function fn? :qualified-keyword qualified-keyword?))
(s/def ::processor-step
(s/merge ::step-common (s/keys :req-un [::processor ::path])))

;; Injector step
(s/def ::inject fn?)
(s/def ::injector-step
(s/merge ::step-common (s/keys :req-un [::inject])))

(s/def ::step
(s/or :tap-step ::tap-step
:processor-step ::processor-step
:injector-step ::injector-step))
80 changes: 80 additions & 0 deletions test/fonda/core_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,83 @@
"it should not call the previous but not the subsequent steps")
(done))
success-cb-throw)))))

(deftest injected-step-should-run-after-injector
(testing "Injecting one step should add the step after the injector"
(async done
(fonda/execute {:initial-ctx {:steps []}}
[{:path [:steps]
:name "processor1"
:processor (fn [{:keys [steps]}]
(conj steps :step1))}
{:inject (fn [_]
{:path [:steps]
:name "injected-step"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step))})
:name "injector1"}
{:path [:steps]
:name "processor2"
:processor (fn [{:keys [steps]}]
(conj steps :step2))}]
exception-cb-throw
(fn [res] (is (= res {:steps [:step1 :injected-step :step2]})) (done))
anomaly-cb-throw))))

(deftest injected-steps-should-run-after-injector
(testing "Injecting multiple steps should add the steps after the injector"
(async done
(fonda/execute {:initial-ctx {:steps []}}
[{:path [:steps]
:name "processor1"
:processor (fn [{:keys [steps]}]
(conj steps :step1))}
{:inject (fn [_]
[{:path [:steps]
:name "injected-step1"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step1))}
{:path [:steps]
:name "injected-step2"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step2))}])
:name "injector1"}
{:path [:steps]
:name "processor2"
:processor (fn [{:keys [steps]}]
(conj steps :step2))}]
exception-cb-throw
(fn [res] (is (= res {:steps [:step1 :injected-step1 :injected-step2 :step2]})) (done))
anomaly-cb-throw))))

(deftest lonely-injector-with-one-step
(testing "Only one injector on the steps"
(async done
(fonda/execute {:initial-ctx {:steps []}}
[{:inject (fn [_]
{:path [:steps]
:name "injected-step"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step))})
:name "injector1"}]
exception-cb-throw
(fn [res] (is (= res {:steps [:injected-step]})) (done))
anomaly-cb-throw))))

(deftest lonely-injector-with-multiple-steps
(testing "Only one injector on the steps"
(async done
(fonda/execute {:initial-ctx {:steps []}}
[{:inject (fn [_]
[{:path [:steps]
:name "injected-step1"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step1))}
{:path [:steps]
:name "injected-step2"
:processor (fn [{:keys [steps]}]
(conj steps :injected-step2))}])
:name "injector1"}]
exception-cb-throw
(fn [res] (is (= res {:steps [:injected-step1 :injected-step2]})) (done))
anomaly-cb-throw))))
5 changes: 5 additions & 0 deletions test/fonda/step_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@
:path [:test]})]
(is (record? step) "the step should be a record")
(is (fn? (:tap step)) "the :tap key should become a function")))

(deftest injector-step-test
(let [step (step/step->record {:inject :cljs.core/println})]
(is (record? step) "the injector should be a record")
(is (fn? (:inject step)) "the :inject key should become a function")))