Skip to content

Commit

Permalink
simplify validated cofx spec
Browse files Browse the repository at this point in the history
  • Loading branch information
mccraigmccraig committed May 22, 2023
1 parent 033e335 commit fc7f76b
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 33 deletions.
122 changes: 103 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ TODO - much documentation expansion

A-frame started life as a port of the [re-frame](https://github.com/day8/re-frame)
event and effect handling machinery to the async domain. It has developed
to become even more data-driven than re-frame, and to give greater control
over the processing of events.
to become even more data-driven than re-frame, and to give greater control
over the processing of events.

Logging and debugging are extremely transparent and,
because every stage of event-processing is data-driven, it's easy to split
different parts of the process over different execution environments - e.g.
because every stage of event-processing is data-driven, it's easy to split
different parts of the process over different execution environments - e.g.
different Kafka Streams apps.

## why?
Expand Down Expand Up @@ -82,8 +82,8 @@ Coeffect handler functions have a signature:

`(fn ([<app> <coeffects>]) ([<app> <coeffects> <data>]))`

The arity providing the `<data>` arg allows data gathered from previous
coeffects and the event to be given to the coeffect handler.
The arity providing the `<data>` arg allows data gathered from previous
coeffects and the event to be given to the coeffect handler.

## effects

Expand All @@ -99,9 +99,9 @@ Effect handler functions have a signature:

## simple example

This example defines a `::get-foo` event, which uses a `::load-foo` cofx
This example defines a `::get-foo` event, which uses a `::load-foo` cofx
to load the object and then returns the loaded object as an `:api/response`
effect.
effect.

``` clojure
(require '[a-frame.core :as af])
Expand All @@ -119,8 +119,8 @@ effect.
{id :id url :url}]
{:id (str url "/" id) :name "foo" :client api-client}))

;; since we can't deref vars on cljs, and we don't want
;; any opaque objects in our simple data, we use a multimethod
;; since we can't deref vars on cljs, and we don't want
;; any opaque objects in our simple data, we use a multimethod
;; to specify the schema validation in inject-validated-cofx
(defmethod mm/validate ::foo
[_ value]
Expand All @@ -131,18 +131,16 @@ effect.
(af/reg-event-fx
::get-foo

;; inject the ::load-foo cofx with an arg pulled from
;; the event and other cofx, validate the value
;; conforms to schema ::foo and set at path ::the-foo
;; in the coeffects
;; inject the ::load-foo cofx with an arg pulled from
;; the event and other cofx, validate the value
;; conforms to schema ::foo
[(af/inject-validated-cofx
::load-foo
{:id #a-frame.cofx/event-path [::foo-id]
:url #a-frame.cofx/path [:config :api-url]}
::foo
::the-foo)]
{:id #af/event-path ::foo-id
:url #af/cofx-path [:config :api-url]}
::foo)]

(fn [{foo ::the-foo :as coeffects}
(fn [{foo ::load-foo :as coeffects}
event]
{:api/response {:foo foo}}))

Expand All @@ -169,6 +167,92 @@ effect.
;; => {:foo {:id "http://foo.com/api/1000", :name "foo" :client :user/api}}
```

of interest is the interceptor log left behind, which details exactly the
interceptor execution history:

``` clojure
(-> @r :a-frame.interceptor-chain/history)

;; => [[:a-frame.std-interceptors/unhandled-error-report
:a-frame.interceptor-chain/enter
:a-frame.interceptor-chain/noop
:_
:a-frame.interceptor-chain/success]
[{:a-frame.interceptor-chain/key :a-frame.cofx/inject-validated-cofx,
:a-frame.cofx/id :user/load-foo,
:a-frame.cofx/path :user/load-foo,
:a-frame.cofx/schema :user/foo,
:a-frame.cofx/arg
{:id
#promisespromises.ctx/path [:a-frame/coeffects :a-frame.coeffect/event :user/foo-id],
:url #promisespromises.ctx/path [:a-frame/coeffects :config :api-url]}}
:a-frame.interceptor-chain/enter
:a-frame.interceptor-chain/execute
{:id "1000", :url "http://foo.com/api"}
:a-frame.interceptor-chain/success]
[{:a-frame.interceptor-chain/key :a-frame.std-interceptors/fx-event-handler,
:a-frame.std-interceptors/pure-handler-key :user/get-foo}
:a-frame.interceptor-chain/enter
:a-frame.interceptor-chain/execute
:_
:a-frame.interceptor-chain/success]
[{:a-frame.interceptor-chain/key :a-frame.std-interceptors/fx-event-handler,
:a-frame.std-interceptors/pure-handler-key :user/get-foo}
:a-frame.interceptor-chain/leave
:a-frame.interceptor-chain/noop
:_
:a-frame.interceptor-chain/success]
[{:a-frame.interceptor-chain/key :a-frame.cofx/inject-validated-cofx,
:a-frame.cofx/id :user/load-foo,
:a-frame.cofx/path :user/load-foo,
:a-frame.cofx/schema :user/foo,
:a-frame.cofx/arg
{:id
#promisespromises.ctx/path [:a-frame/coeffects :a-frame.coeffect/event :user/foo-id],
:url #promisespromises.ctx/path [:a-frame/coeffects :config :api-url]}}
:a-frame.interceptor-chain/leave
:a-frame.interceptor-chain/noop
:_
:a-frame.interceptor-chain/success]
[:a-frame.std-interceptors/unhandled-error-report
:a-frame.interceptor-chain/leave
:a-frame.interceptor-chain/noop
:_
:a-frame.interceptor-chain/success]]
```

note the second entry:

``` clojure
[{:a-frame.interceptor-chain/key :a-frame.cofx/inject-validated-cofx,
:a-frame.cofx/id :user/load-foo,
:a-frame.cofx/path :user/load-foo,
:a-frame.cofx/schema :user/foo,
:a-frame.cofx/arg
{:id
#promisespromises.ctx/path [:a-frame/coeffects :a-frame.coeffect/event :user/foo-id],
:url #promisespromises.ctx/path [:a-frame/coeffects :config :api-url]}}
:a-frame.interceptor-chain/enter
:a-frame.interceptor-chain/execute
{:id "1000", :url "http://foo.com/api"}
:a-frame.interceptor-chain/success]
```

each log entry has the form:

`[<cofx-spec> <interceptor-op> <interceptor-action> <data-arg> <op-outcome>]`

so you can see both the specification of the cofx data arg `:a-frame.cofx/arg`
and the resolved `<data-arg>`

## error handling

<TODO>

## effects now or later

<TODO>

<!-- TODO -->
<!-- - dispatch-* coeffects - returning a result to a path in the coeffects -->
<!-- - do-fx interceptor should add an :a-frame/effects-results key to -->
Expand Down
14 changes: 11 additions & 3 deletions src/a_frame/cofx.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,17 @@

(defn inject-validated-cofx
"a cofx with a result with a defined schema to be
injected at a given path"
([id schema path]
(inject-validated-cofx id nil schema path))
injected
- `id` : the cofx id
- `:schema` : the schema of the injected value
- `:arg-spec` : specification of the cofx handler arg
- `:path` : optional path in the coeffects to inject the value.
defaults to `id`"
([id schema]
(inject-validated-cofx id nil schema id))
([id arg-spec schema]
(inject-validated-cofx id arg-spec schema id))
([id arg-spec schema path]
(cond->
{::interceptor-chain/key ::inject-validated-cofx
Expand Down
2 changes: 1 addition & 1 deletion src/a_frame/interceptor_chain/data/data_path.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
#?(:clj
(defn print-data-path
[dp ^java.io.Writer w]
(.write w "#promisespromises.ctx/path ")
(.write w "#a-frame.ctx/path ")
(print-method (-path dp) w)))

#?(:clj
Expand Down
11 changes: 10 additions & 1 deletion src/data_readers.cljc
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
{a-frame.cofx/path a-frame.cofx.data.tag-readers/read-cofx-path
af.cofx/p a-frame.cofx.data.tag-readers/read-cofx-path
a-frame/cofx-path a-frame.cofx.data.tag-readers/read-cofx-path
af/cofx-path a-frame.cofx.data.tag-readers/read-cofx-path
af/cofxp a-frame.cofx.data.tag-readers/read-cofx-path

a-frame.cofx/event-path a-frame.cofx.data.tag-readers/read-event-path
af.cofx/evp a-frame.cofx.data.tag-readers/read-event-path
a-frame/event-path a-frame.cofx.data.tag-readers/read-event-path
af/event-path a-frame.cofx.data.tag-readers/read-event-path
af/evp a-frame.cofx.data.tag-readers/read-event-path

a-frame.ctx/path a-frame.interceptor-chain.data.tag-readers/read-ctx-path
af.ctx/p a-frame.interceptor-chain.data.tag-readers/read-ctx-path}
af.ctx/p a-frame.interceptor-chain.data.tag-readers/read-ctx-path
a-frame/ctx-path a-frame.interceptor-chain.data.tag-readers/read-ctx-path
af/ctx-path a-frame.interceptor-chain.data.tag-readers/read-ctx-path
af/ctxp a-frame.interceptor-chain.data.tag-readers/read-ctx-path}
14 changes: 5 additions & 9 deletions test/a_frame/cofx_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@

interceptors [(sut/inject-validated-cofx
cofx-key
::int
cofx-key)]
::int)]

int-r (interceptor-chain/execute
::app
Expand All @@ -163,8 +162,7 @@

interceptors [(sut/inject-validated-cofx
cofx-key
::string
cofx-key)]
::string)]

[k e] (prpr/merge-always
(interceptor-chain/execute
Expand All @@ -189,7 +187,7 @@
int-r (interceptor-chain/execute
::app
::a-frame
[(sut/inject-validated-cofx cofx-key 100 ::=100 cofx-key)]
[(sut/inject-validated-cofx cofx-key 100 ::=100)]
init-int-ctx)]

(is (= (assoc
Expand Down Expand Up @@ -225,15 +223,13 @@
::a-frame
[(sut/inject-validated-cofx
static-cofx-key
::keyword
static-cofx-key)
::keyword)

(sut/inject-validated-cofx
resolved-cofx-key
{:a #a-frame.cofx/path [::inject-validated-cofx-1-arg-resolver-static]
:b #a-frame.cofx/event-path [1]}
::any
resolved-cofx-key)]
::any)]
init-int-ctx)]

(is (= (assoc
Expand Down

0 comments on commit fc7f76b

Please sign in to comment.