clojureplumatic-schema

Default values for prismatic/schema coerce instead of error messages


Using prismatic/schema coerce is it possible to have default values when the coercion fails instead of the error message.

I have a value in a csv file which could be blank (nil) or s/Int. At the moment with the below code I get this for blanks:

 #schema.utils.ErrorContainer{:error (not (integer? nil))}

code:

(def answers (slurp "excel/answers.csv"))
(def answers-field-schemas [s/Int s/Int s/Str s/Str s/Str s/Int s/Str s/Int s/Str s/Int s/Str s/Int s/Str s/Int s/Str])

(def answers-field-coercers
  (mapv coerce/coercer
    answers-field-schemas
    (repeat coerce/string-coercion-matcher)))

(defn answers-coerce-fields [fields]
  (mapv #(%1 %2) answers-field-coercers fields))

(def answers->data (map answers-coerce-fields (csv/parse-csv answers :end-of-line "\r")))

Solution

  • 1 . Error you get is not coercion error, but validation error. Values must conform initial schema.

    2 . To fix it, you need to loose your schema for fields that may be nil. Let's say it's second field:

    (def answers-field-schemas [s/Int (s/maybe s/Int) ...])
    

    At this point you will get nils instead of errors for nil fields:

    user> (answers-coerce-fields ["1" nil])
    [1 nil]
    

    3 . If you really want default values instead of nils after coercion, you will need custom coercion matcher. Something like this:

    (import 'schema.core.Maybe)
    
    (defn field-matcher-with-default [default]
      (fn [s]
        (let [string-coercion-fn (or (coerce/string-coercion-matcher s)
                                     identity)
              maybe-coercion-fn (if (instance? schema.core.Maybe s)
                                  (fnil identity default)
                                  identity)]
          (comp
           string-coercion-fn
           maybe-coercion-fn))))
    

    Also modify previous coercers as follows:

    (def answers-field-coercers
      (mapv coerce/coercer
            answers-field-schemas
            ;; your default here
            (repeat (field-matcher-with-default -1))))
    

    and then:

    user> (answers-coerce-fields ["1" nil])
    [1 -1]
    

    Note that default value must conform schema as well, so it's not possible to set default value of type String for schema (s/maybe s/Int).