Fork me on GitHub
#schema
<
2020-03-17
>
Braden Shepherdson17:03:45

I'm struggling somewhat to understand how to set up a custom coercion in schema.

Braden Shepherdson17:03:30

I want a schema and coercion that wants to accept a string "2020-03-18" and coerces it to a LocalDate

nickmbailey17:03:39

we do that at the json layer with 'cheshire.generate/add-encoder'

nickmbailey17:03:01

err sorry that's for serialization, you want coercion

nickmbailey17:03:49

we use 'ring.swagger.coerce/coerce'

nickmbailey17:03:07

(defmethod coerce/time-matcher LocalDate
  [_]
  #(if (jt/local-date? %)
     %
     (try
       (jt/local-date local-date-format %)
       (catch Exception original-exc
         (let [result (try-parse-as-date %)]
           (if (nil? result)
             (throw original-exc)
             result))))))

ikitommi18:03:08

@braden.shepherdson just opened a old project which uses Schema + Joda, mapping look like this:

;;
;; joda-time
;;

(defn ->DateTime [date] (if (instance? Date date) (tc/from-date date) date))

(defn parse-date-time ^DateTime [date] (tf/parse (tf/formatters :date-time-parser) (->DateTime date)))
(defn parse-date ^DateTime [date] (tf/parse-local-date (tf/formatters :date) (->DateTime date)))
(defn parse-time ^DateTime [date] (tf/parse-local-time (tf/formatters :time-parser) (->DateTime date)))

(defmethod schema-tools.swagger.core/transform-type DateTime [_ _] {:type "string" :format "date-time"})
(defmethod schema-tools.swagger.core/transform-type LocalDate [_ _] {:type "string" :format "date"})
(defmethod schema-tools.swagger.core/transform-type LocalTime [_ _] {:type "string" :format "time"})

;;
;; helpers
;;

(defn cond-matcher [preds]
  (fn [x]
    (or
      (some
        (fn [[f1 f2]]
          (if (f1 x) (f2 x)))
        preds)
      x)))

(def keyword->int
  {s/Int (cond-matcher
           {string? stc/string->long
            number? sc/safe-long-cast
            keyword? (fn [x]
                       (try
                         (Long/valueOf (name x))
                         (catch Exception _ x)))})})

(def keyword->string
  {String (cond-matcher {keyword? name})})

(def string->joda
  {DateTime (cond-matcher {string? parse-date-time})
   LocalDate (cond-matcher {string? parse-date})
   LocalTime (cond-matcher {string? parse-time})})

(def json-coercion-matcher
  (some-fn keyword->int keyword->string string->joda stc/json-coercion-matcher))

(def string-coercion-matcher
  (some-fn keyword->int keyword->string string->joda stc/string-coercion-matcher))

Braden Shepherdson18:03:00

another example of custom coercion - what about forcing things to uppercase or lowercase?

Braden Shepherdson18:03:25

I'm not super concerned about that since this is an API intended to be target by my own code, but still.