clojureplumatic-schema

How do I generate user friendly validation messages with plumatic/schema?


I would like to be able to generate user-friendly or specify custom error messages for validation errors in these schemas:

(def Uuid (s/constrained String #(re-matches #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" (name %))))
(def FirstName s/Str)
(def LastName s/Str)

(s/defschema Person {(s/required-key :id)         Uuid,
                     (s/required-key :first-name) FirstName,
                     (s/required-key :last-name)  LastName})

Valid schema:

{
 :uuid "e143499c-1257-41e4-b951-c9e586994ff9" 
 :first-name "john" 
 :last-name "smith"
}

Invalid schema:

{
 :uuid "" 
 :first-name nil 
 :last-name nil
}

Invalid schema - Errors:

{
 "id" : "(not (app.person/fn--4881 \"\"))",
 "first-name" : "(not (instance? java.lang.String nil))"
 "last-name" : "(not (instance? java.lang.String nil))"
}

I would like to be able to generate something a bit more readable to non-programmers, for example:

{
 "id" : "invalid uuid",
 "first-name" : "must be a string"
 "last-name" : "must be a string"
}

Solution

  • Funnily exactly this was released as a library a few days ago.

    See:

    https://github.com/siilisolutions/humanize

    First you also need to tag your Uuid schema so you can match it later on:

    ;; Note the last param I added: 
    (def Uuid (sc/constrained
                String
                #(re-matches #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
                             (name %))
                'UUID))
    
    (require '[schema.core :as sc]
             '[humanize.schema :as hs])
    
    (#'hs/explain (sc/check Person {:id "foo"
                                    :first-name "foo"
                                    :last-name 3})
      (fn [x]
        (clojure.core.match/match
          x
          ['not ['UUID xx]]
          (str xx " is not a valid UUID")
    
          :else x)))
    

    Results in:

    => {:id "foo is not a valid UUID", :last-name "'3' is not a string but it should be."}
    

    Note, it needed a little trick since hs/explain is private unfortunately.