Fork me on GitHub
#expound
<
2021-07-20
>
Quentin Le Guennec13:07:15

I can't get expound to work on clojurescript running on chrome. Can anyone help?

bbrinck13:07:00

@quentin.leguennec1 Can you expand a bit on this? What have you tried? What are you expecting to see? And what do you see now?

Quentin Le Guennec14:07:29

@bbrinck Yes. I'm using https://github.com/nedap/speced.def and I'm expecting to see the expound formatted message in the chrome console when there is a spec mismatch. I have this preloaded:

(ns analis-desktop.preload
  (:require
   [js-deps-bundle]
   [expound.alpha :as expound]
   [cljs.spec.alpha :as s]
   [cljs.repl :as repl]))

(println "requiring js bundle before re-frame-10x...")

(set! s/*explain-out* expound/printer)

(def devtools-error-formatter
  "Uses cljs.repl utilities to format ExceptionInfo objects in Chrome devtools console."
  #js{:header
      (fn [object _config]
        (when (instance? ExceptionInfo object)
          (let [message (some->> (repl/error->str object)
                                 (re-find #"[^\n]+"))]
            #js["span" message])))
      :hasBody (constantly true)
      :body    (fn [object _config]
                 #js["div" (repl/error->str object)])})

(defonce _
  (some-> js/window.devtoolsFormatters
          (.unshift devtools-error-formatter)))

Quentin Le Guennec14:07:55

(s/explain int? :hi) correctly displays with the expound formatting

Quentin Le Guennec14:07:37

spec mismatchs display this:

REPL eval error 
Execution error (ExceptionInfo) at (<cljs repl>:1).
Execution error (ExceptionInfo) at (<cljs repl>:1).
Validation failed

Quentin Le Guennec14:07:33

(I'm using emacs, lein, figwheel, chrome)

bbrinck14:07:55

By “spec mismatchs”, do you mean the error that occurs when you call a specced function with invalid arguments?

bbrinck14:07:53

Two things to try: 1. Can you double check the error handler is working? Perhaps by just raising some other ExpectionInfo object in your code and making sure your custom formatter is invoked?

bbrinck14:07:36

2. Can you remove speced.def from the repro and see what happens when you use s/fdef?

Quentin Le Guennec14:07:58

(ExceptionInfo. "hello") doesn't do anything, neither to the repl on the chrome console

Quentin Le Guennec14:07:59

oh nvm (throw (ExceptionInfo. "hello")) correctly throws an error

bbrinck14:07:42

Does the devtools-error-formatter above print it as you expected in the console?

Quentin Le Guennec14:07:03

no, it shows:

react_devtools_backend.js:2574 REPL eval error 
Execution error (ExceptionInfo) at (<cljs repl>:1).
Execution error (ExceptionInfo) at (<cljs repl>:1).
hello

bbrinck14:07:12

I see. So although there may be an issue with the Expound config, I think the first thing is to debug why (throw (ExceptionInfo. "hello")) isn’t being formatted by the devtools-error-formatter

Quentin Le Guennec14:07:16

devtools-error-formatter, isn't called at all, console.log calls don't do anything in the :header function

bbrinck14:07:22

Interesting. I wonder if the defonce is being called correctly. Or if you can inspect the value of js/window.devtoolsFormatters once the page is loaded and see what is in there.

Quentin Le Guennec14:07:55

#js[#js{:header #object[header], :hasBody #object[G__11224], :body #object[body]} #js{:header #object[header], :hasBody #object[G__11224], :body #...

bbrinck14:07:15

Can you confirm those values are not other formatters that are pre-installed?

bbrinck14:07:55

It’s hard to say just from looking at what you showed above, since the values are opaque objects

Quentin Le Guennec14:07:09

Yep, I named the header function custom-header-function and : #js[#js{:header #object[analis_desktop$preload$custom_header_function], :hasBody #object[G__11224], :body

Quentin Le Guennec14:07:24

Why unshift though? Can't I just make a new array?

Quentin Le Guennec14:07:10

Oh nvm, it's displaying my console.log calls now

bbrinck14:07:37

Since s/window.devtoolsFormatters is a mutable JS array, we need to mutate it with unshift. We could make a new array, but we’d need to assign it somewhere

bbrinck14:07:49

Oh OK, that’s good to know that the console logs are printing. Do you see the custom message somewhere? It might be useful to add some weird text to it to make sure it’s easy to see e.g. "--- debugging----"

Quentin Le Guennec14:07:49

Yeah it seems like the defonce _ call is not working

Quentin Le Guennec14:07:58

It works when I remove it

Quentin Le Guennec14:07:05

I don't see the result of the :body call though

Quentin Le Guennec14:07:14

#js{:header
      (fn custom-header-function [object _config]
        (.log js/console "---debugging---" (instance? ExceptionInfo object))
        (when (instance? ExceptionInfo object)
          (let [message (some->> (repl/error->str object)
                                 (re-find #"[^\n]+"))]
            #js["span" message])))
      :hasBody (constantly true)
      :body    (fn [object _config]
                 (.log js/console "hello")
                 #js["div" (repl/error->str object)])}

bbrinck14:07:58

OK, maybe you can simplify by just printing out random values to make sure header and body are being shown. Instead of #js["span" message], do #js["span" "test header"] and instead of #js["div" (repl/error->str object)] do #js["div" "test body"]

Quentin Le Guennec14:07:02

The console.log call only runs when I expand the body but I guess it's intended

Quentin Le Guennec14:07:58

So I guess there's no issue in that part

bbrinck14:07:03

Excellent!

bbrinck14:07:38

So, if you put the old code back, then do something like (throw (ex-info "some error" {:a 1 :b 2})), then expand the body, you should see details about the error. Does that all work?

Quentin Le Guennec14:07:40

REPL eval error #error {:message "some error", :data {:a 1, 😛 2}}

bbrinck14:07:43

OK, then next, can you do something like this (untested, I don’t have a Clojure repl handy)

(s/fdef my-inc
  :args (s/cat :x number?))
(defn my-inc [x] (+ x 1))

bbrinck14:07:56

Then call (my-inc "foo")

bbrinck14:07:15

then expand the body, see what happens

Quentin Le Guennec14:07:02

Evaluating this in the repl doesn't throw anything:

(do
  (s/fdef my-inc
    :args (s/cat :x number?))

  (defn my-inc [x] (+ x 1))

  (my-inc "foo"))

bbrinck14:07:14

oh, shoot, I forgot

(stest/instrument `my-inc)

bbrinck14:07:58

then (my-inc "foo")

Quentin Le Guennec14:07:13

I got this in the chrome console:

#error {:message 
"Call to #'analis-des … not conform to spec."
, :data 
{:cljs.spec.alpha/problems [
{…}

Quentin Le Guennec14:07:26

A standard ex-info message from what I understand, not formatted by expound

bbrinck14:07:50

Is that in the body when you expand it?

bbrinck15:07:19

The devtools-error-formatter code only sets the header and body, it won’t change what is displayed in the console itself.

Quentin Le Guennec15:07:41

Well there are several things going on, it seems

bbrinck15:07:30

Got it. OK, so it looks to me that there are a few things going on here. My guess is that if you removed that defonce, it means that each time you eval this code, it’s adding an additional formatter, so you’re seeing the results of old and new formatters

bbrinck15:07:40

I don’t know if you can hard-reload the page or something to just see one

bbrinck15:07:25

I could be mistaken and you’re seeing two outputs for other reasons.

bbrinck15:07:47

It looks like for some reason, the following code isn’t taking effect (set! s/*explain-out* expound/printer). Can you double check that is being called? You might even copy/paste it into the body function to make sure it’s being called before you call repl/error->str

bbrinck15:07:16

Or you could try

clojure
:body    (fn [object _config]
  #js["div" (binding [s/*explain-out* expound/printer]
  (repl/error->str object))
 ])

Quentin Le Guennec16:07:58

@bbrinck I partially got it. I actually had to move the code away from the preload and it works even with the defonce. I still only have "Validation error" for speced, though, but I guess I should directly contact the author.

👍 2
bbrinck16:07:45

It’s possible the issue is that specced may need expound to be set up during macro-expansion time, which confusingly happens in Clojure, not Clojurescript.

bbrinck16:07:09

i.e. you might have to set up a .cljc file and then do something like

(require '[expound.alpha :as expound]) #?(:clj (set! clojure.spec.alpha/*explain-out* expound.alpha/printer))

bbrinck16:07:35

And have that happen before you start using any specced macros. That’s a total guess though.

Quentin Le Guennec18:07:49

Hmm I did that and the issue remains

Quentin Le Guennec18:07:00

It seems like it's rather chrome not displaying the complete message for speced specs though, because this gets printed in the REPL, and that looks like a expound string: #error {:message "Validation failed", :data {:explanation "-- Spec failed --------------------\n\n :a\n\nshould satisfy\n\n string?\n\n-------------------------\nDetected 1 error\n", :spec-object #object[cljs$core$string_QMARK_], :spec cljs.core/string?, :quoted-spec cljs.core/string?, :faulty-value-object :a, :quoted-faulty-value a, :faulty-value a}}

bbrinck18:07:04

Yep, that is an expound message in the :explanation

bbrinck18:07:42

But is that what is thrown from specced?

bbrinck18:07:39

error->str (or rather ex-str, which is called by it) expects a normal spec error. If specced is already changing the error, you’d need to adjust your handler to specifically deal with specced data

bbrinck18:07:23

I haven’t tested this, but something like

clojure
:body    (fn [object _config]
                 (.log js/console "hello")
                 #js["div" (get-in (repl/Error->map object) [:data :explanation])])