Fork me on GitHub
Yehonathan Sharvit11:03:18

When one uses clojure.spec to validates the body of an API request, how can we returns user-friendly errors in case the body doesn’t conform?

Yehonathan Sharvit11:03:36

I mean s/explain-str is nice for developers but not for users!!!


@viebel What about using s/explain-data and formatting it however you want?

Yehonathan Sharvit11:03:23

How would you do that @yonatanel ?

Yehonathan Sharvit11:03:38

Let’s take a simple example of a signup request


I guess it's the author responsibility to define higher level predicates that can be later examined more easily than (>= (count %) 8)


Something like long-enough?

Yehonathan Sharvit11:03:47

Nice idea. How would you manage the mapping between predicates/specs and a string that describe them?


Not sure right now, but I think you are right in that explain is not really meant for public data-oriented apis.

Yehonathan Sharvit11:03:29

It means that I have to manually conform the data? 😞


I'm not sure what you mean by that and how it's related to the error message

Yehonathan Sharvit11:03:42

I mean that I can write code that checks if the password is long enough and returns an appropriate error message

Yehonathan Sharvit11:03:15

I’d need to write such a piece of code in addition to the specs definitions - which is quite disappointing


Would you like each predicate to optionally have an explanation string or something like that?

Yehonathan Sharvit11:03:55

But I am curious to know if spec is designed for that


You kinda have all the building blocks for that, and it might be good to separate error message from spec itself. You could have multiple mappings from a spec to error message, one for public api, another for a kafka consumer etc, all using the same registered (s/def ::long-enough ...) spec.


I still have to go about trying spec out for myself, but for a similar usecase I made a simple validation function, returning a list with either only true, or false and an end-user friendly error message.

Yehonathan Sharvit17:03:48

Could you be more specific @gklijs


Well, I wouldn’t know since I’m not using spec yet, I just did it like this:

(defn valid-registration-map?
    (not (map? registration-map)) '(false "Input is not a map")
    (not (contains? registration-map :username)) '(false "Input is missing the :username key.")
    (not (contains? registration-map :password)) '(false "Input is missing the :password key.")
    (< (count (:username registration-map)) 8) '(false "Username should have a minimal of 8 characters")
    (< (count (:password registration-map)) 8) '(false "Password should have a minimal of 8 characters")
    :else '(true)))


In spec you explain separately from validating


With forms, there's also the issue that if you validate the whole map, you can't indicate that there's an error with a particular key in the same way s/keys does (unless that's changed?)


We use explain-data and a map of symbols or forms to messages -- and a generic function that walks the explanations and finds matching messages. That way you can choose how generic or how specific the message is by how much of the form of the spec it matches. For example, if you just want a generic bad password message, just map 'password to the message. Otherwise map more pieces of the spec forms to different messages.

Yehonathan Sharvit18:03:00

@seancorfield could you share a code snippet?


I'm on my phone so, no. I'll try to remember tomorrow when I'm back at work. But this is what the Clojure/core folks have been encouraging us to do with spec: walk the explanation data and map it to messages or error codes or whatever. You have a lot of useful information there.


The path, the predicate, etc.

Yehonathan Sharvit18:03:14

That looks very cool!

Yehonathan Sharvit18:03:27

I’ll ping you tomorrow @seancorfield !

Yehonathan Sharvit19:03:29

@alexmiller any interesting insights of how to generate user-friendly error messages from spec. See

Alex Miller (Clojure team)20:03:10

Advice from Sean above seems right

Alex Miller (Clojure team)20:03:40

Or bind a custom explain printer