Awesome
Inspectable
In your spec table.
A bunch of tools to help improve your repl experience when using clojure.spec with clojure and clojurescript.
Installation
For clojure make sure you are using [org.clojure/clojure "1.9.0-alpha17"]
or newer.
For clojurescript make sure you are using [org.clojure/clojurescript "1.9.854"]
or newer.
To include the library add the following to your :dependencies
.
[inspectable "0.2.2"]
Usage
For clojure :
user> (require '[inspectable.repl :refer [why browse-spec]])
For clojurescript :
;; In the clojure repl
user> (use 'inspectable.repl)
user> (start-cljs)
;; In your clojurescript repl
cljs.user> (require '[inspectable.cljs-repl :refer-macros [why] :refer [browse-spec]])
Currently inspectable provides two tools: browse-spec
and why
. See below to understand how they can help you.
The spec browser (browse-spec)
The spec browser lets you explore your spec registry through a java swing graphical interface. You can invoke it with different type of arguments:
(browse-spec) ;; open the browser showing all specs in the registry
(browse-spec "ring") ;; open the browser showing specs matching "ring" regex
(browse-spec :ring/request) ;; open the browser pointing to :ring/request spec
(browse-spec 'clojure.core/let) ;; open the browser pointing to 'clojure.core/let spec
For example, suppose you are working with ring and have ring-spec loaded,
then you can use (browse-spec "ring")
to see a list of all specs in the registry that matches "ring" in their names.
Once in the browser, you can click on the specs to navigate down to sub specs, or use the top bar to navigate back to previous visited specs:
<img src="/doc/images/browser-ring-request.png?raw=true"/>As you can see, browsing a spec shows you a pretty print of the spec form together with a generated spec sample.
<img src="/doc/images/browser-ring-server-port.png?raw=true"/>Inspectable spec browser also supports browsing multi-specs:
<img src="/doc/images/browser-multi.png?raw=true"/>In case a sample can't be generated the exception will be displayed intead of the example
<img src="/doc/images/browser-sample-not.png?raw=true"/>Specs fail explain (why)
Another useful tool provided by inspectable is why
.
You can use why
to understand why clojure.spec is complaining about a spec in three different situations.
First, you can use why
to help you understand the output of s/explain-data
(def bad-request (ring.mock.request/request :get "htp://localhost:69000/test"))
(s/valid? :ring/request bad-request) ;; => false
(why (s/explain-data :ring/request bad-request))
<img src="/doc/images/ring-req-fail-pp.png?raw=true"/>
In this case the pprint is enough, but if the data structure is a big and deeply nested one, try using the collapsible tree:
<img src="/doc/images/ring-req-fail-tree.png?raw=true"/>Second, why
can help in situations like calling an instrumented function that fails.
See Integrating inspectable with your repl for a way of automatically calling why on this situations.
Suppose we are working with events as defined in clojure spec guide and some function :
(s/fdef only-with-code
:args (s/cat :code :error/code
:events (s/coll-of :event/event))
:ret :event/event)
(defn only-with-code [code events]
...)
;; instrument it
(stest/instrument)
;; try to call it with some args
(why
(only-with-code 4
[{:event/type :event/search
:event/timestamp 0
:search/url "/home"}
{:event/type :event/search
:event/timestamp 5.5
:search/url "/home"}
{:event/type :event/error
:error/code 4
:search/url "/home"}
{:event/type :event/search
:search/url "/about"}]))
and you will get :
<img src="/doc/images/fn-instrument-fail.png?raw=true"/>Third, why
can also be used to catch macro expansion exceptions like in the case of:
(why (let [a 1
b 2
3]
(+ a b c)))
will show you :
<img src="/doc/images/let-fail.png?raw=true"/>See Integrating inspectable with your repl for a way of automatically calling why on this situations.
Integrating inspectable with your repl
You can call (inspectable.repl/install) to install inspectable on your current repl so every time spec
throws a exceptions why
will be automatically applied over it.
(inspectable.repl/install)
There is a function inspectable.repl/repl-caught
you can use for the same purpose if you are starting
your own sub repl.
Starting a new repl :
(clojure.main/repl :caught inspectable.repl/repl-caught)
Workflows integration ideas
Re-frame
If you are using re-frame and you have specs for your db you can modify your spec check middleware like :
(defn check-and-throw
"throw an exception if db doesn't match the spec"
[a-spec db]
(when-not (s/valid? a-spec db)
(why (s/explain-data a-spec db)))) ;; <---- use why here
(def check-spec (after (partial check-and-throw :my-app.db/db)))
Cider
If you are using Cider and Clojurescript
(setq cider-cljs-lein-repl "(do (use 'inspectable.repl) (start-cljs))")
so after your cider-jack-in-clojurescript
everything is ready.
Related work
If you are interested in understanding specs when they fail also checkout expound!
Roadmap
- Multiple themes
- Reference to the caller for instrumented functions fails.
- Test and add instructions for React Native