Fork me on GitHub
#clojure-spec
<
2024-04-10
>
Nim Sadeh13:04:11

I was a bit surprised at what happens when you combine spec with pre/post conditions. I use this sparingly at the boundaries of API calls/object coercions. I expected failed coercions to give useful information, but it doesn't. Is there a better way to guard structured at the boundaries? I ended up writing the following util function that does what I was hoping that pre/post conditions conditions would do, but I feel like I am doing something "wrong" by not just using the built-in functions. Why do they behave like that, and why is what I am doing not a good idea?

(defn defend-spec
  [spec candidate]
  (if-let [explained (s/explain-data spec candidate)]
    (throw (ex-info "Object failed to conform to spec" explained))
    candidate))

seancorfield15:04:54

pre/post use assertions which are Error in the Throwable hierarchy and really aren't expected to be caught. ex-info produces an Exception which is reasonable to catch. I don't see pre/post used much in the wild, partly because assertions are generally "stop the world" (and a lot of people think they should be turned off in production -- I disagree), and partly because they don't produce very good information. Some people will do this instead: (assert (s/valid? spec candidate) (s/explain-str spec candidate)) -- that will still be an Error but at least the message will now be a useful(?) explanation of the failure. Note that some people think it's perfectly fine to catch Throwable but you'll see a lot of advice saying don't do that, only catch Exception.

Nim Sadeh14:04:05

Why would they be turned off? I probably want the world to stop if data coming in outside my system is wrong. I can't necessarily predict what it will do in my database

seancorfield15:04:24

Well, exactly... But some folks think assertions are for dev/test only :man-shrugging::skin-tone-2:

escherize18:04:42

Performance is one reason someone might want to turn them off