bhb / expound

Human-optimized error messages for clojure.spec
Eclipse Public License 1.0
928 stars 24 forks source link

consider shorting down error message on sequence failure #110

Open carocad opened 6 years ago

carocad commented 6 years ago

For one of my api endpoints I have a quite long response which is compressed of several so-called steps. Each step is an object on its own.

The problem arises when one of the last item of the validation fails. It seems that expound simply replaces each object in the sequence with ... which is fine for short sequences but for longer ones becomes a problem (See output below)

-- Spec failed --------------------

  {:code ...,
   :uuid ...,
   :waypoints ...,
   :route
   {:distance ...,
    :duration ...,
    :steps
    (...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     ...
     {:mode "transit",
            ^^^^^^^^^
      :maneuver ...,
      :distance ...,
      :duration ...,
      :geometry ...,
      :name ...}
     ...
     ...)}}

should be: "walking"

-- Relevant specs -------

:walk/mode:
  #{"walking"}
:walk/step:
  (clojure.spec.alpha/merge
   (clojure.spec.alpha/keys :req-un [:walk/mode :walk/maneuver])
   :step/base)
:hiposfer.kamal.specs.directions/step:
  (clojure.spec.alpha/or :walk :walk/step :transit :transit/step)
:hiposfer.kamal.specs.directions/steps:
  (clojure.spec.alpha/coll-of
   :hiposfer.kamal.specs.directions/step
   :kind
   clojure.core/sequential?)
:hiposfer.kamal.specs.directions/route:
  (clojure.spec.alpha/keys
   :req-un
   [:hiposfer.kamal.specs.directions/distance
    :hiposfer.kamal.specs.directions/duration
    :hiposfer.kamal.specs.directions/steps])
:hiposfer.kamal.specs.directions/response:
  (clojure.spec.alpha/keys
   :req-un
   [:hiposfer.kamal.specs.directions/code]
   :opt-un
   [:hiposfer.kamal.specs.directions/waypoints
    :hiposfer.kamal.specs.directions/route])
bhb commented 6 years ago

Thanks for submitting this! I agree, the output is quite verbose in cases like this. I’ll think more about how I can shorten the output without losing useful context.

carocad commented 6 years ago

@bhb if I may add. Would it also be possible to add a bit more context to the error report?

In this particular case, :mode "transit" is a key-value pair that is repeated in several steps so the report showing only the failing value is not enough to know exactly which part is failing.

For that I had to switch back to Clojure explain since there I was able to see exactly which values where failing :(

bhb commented 6 years ago

Thanks for that additional information! Yes, I’ll also look at how to best add more context in this case.

FWIW, if Expound is omitting too much information, you can use “custom-printer” with “:show-valid-values?” set to true. The API is a bit inconsistent right now (I plan to simplify things for the beta) so let me know if you have any trouble getting this working.

bhb commented 6 years ago

@carocad Here's the code for that:

(binding [s/*explain-out* (expound/custom-printer {:show-valid-values? true})]
  (s/explain (s/coll-of int?) [1 2 :3]))
;;-- Spec failed --------------------
;;
;;  [1 2 :3]
;;       ^^
;;
;;should satisfy
;;
;;  int?
;;
;;-------------------------
;;Detected 1 error

I realize that's not intuitive, but I'll be making this easier at some point. Until then, it may be easiest to wrap it up in a function. Hope that helps!

carocad commented 6 years ago

@bhb Thanks for the info

Using show-valid-values together with the figwheel-theme do improve the situation a bit. However, now the output is gigantic (due to the size of the response that I have) :(

I think that ideally the failing value should show a bit of the context around (enough to know where the problem is) but not the complete datastructure; otherwise the failing value would get lost among the noise.

I know that is a very hard thing to do, though :/

PS: I avoided pasting the error message here, so please let me know if it would help you somehow

bhb commented 6 years ago

Yes, it’s a bit tricky. Thanks for letting me know about your use case . It’s very helpful to know which cases need improvement. I’ll think more about this.

carocad commented 6 years ago

@bhb i just had an idea about how to approach this problem.

Instead of using 'show valid values' I think the concept of depthwould be more appropriate. Basically instead of guessing how much context does a user need to understand the problem, let the user directly tell you.

This is (not by surprise) the same way that most Clojure stacktrace libraries work since those also contain lots of data.

Hope it helps :)

bhb commented 6 years ago

Ah, that’s a good idea, thanks!

kelvinqian00 commented 5 years ago

I'm also experiencing the same issue as @carocad and my hacked-together solution was simple: capture the printed string from expound/custom-printer with with-out-string, search for empty lines with the following regex:

#"(?<=\n)\s+\.\.\.\n"

and replace each matching string with an empty string. It's a decent solution for my limited use-case, but ideally a similar regex replace would be implemented in the printer itself.

bhb commented 5 years ago

@kelvinqian00 Thanks for posting this workaround! I agree this is not ideal. I’m thinking about ways to make this behavior optional.