Fork me on GitHub
#malli
<
2021-02-09
>
danielneal11:02:46

What's the best way to define a string format so that it comes through to swagger

danielneal11:02:18

(swagger/transform [:map
                    [:a {:swagger/format "date-time"} string?]])
; => {:type "object", :properties {:a {:type "string", :format "date-time"}}, :required [:a]}

danielneal11:02:39

this works, but I'm wondering if there's a better way that also has validation

ikitommi11:02:00

maybe split it:

(def DateTimeString [:string {:swagger/format "date-time"}])

(swagger/transform [:map [:a DateTimeString]])
; => {:type "object", :properties {:a {:type "string", :format "date-time"}}, :required [:a]}

ikitommi11:02:57

what do you mean by “also has validation”?

danielneal11:02:56

like maybe a [:fn #(java.time.Instant/parse %)] or something?

ikitommi11:02:45

that would return an Instant, not string.

ikitommi11:02:15

after the date schemas are implemented, it would be:

(swagger/transform [:map [:a :instant]])
; => {:type "object", :properties {:a {:type "string", :format "date-time"}}, :required [:a]}

danielneal11:02:46

ah, that would be ideal

ikitommi12:02:15

but, do you want Instant as a result or just a string that is formatted like an instant?

danielneal12:02:16

haha that's a good question. For consumers of the api, and in the docs etc it should be a string that is formatted like an instant. It would be nice if when we coerce parameters, we could get back a real instant.

danielneal12:02:41

Is what :string/decode is used for

ikitommi13:02:51

@danieleneal if you want a full custom type, here’s a sample:

(ns demo
  (:require [malli.core :as m]
            [malli.error :as me]
            [malli.generator :as mg]
            [malli.transform :as mt]
            [malli.json-schema :as json-schema]
            [clojure.test.check.generators :as gen])
  (:import [java.time Instant]
           [java.util Date]))

(def instant
  (let [string->instant #(if (string? %) (Instant/parse %))]
    (m/-simple-schema
      {:type 'instant
       :pred (partial instance? java.time.Instant)
       :type-properties {:error/message "should be instant"
                         :decode/string string->instant
                         :decode/json string->instant
                         :json-schema {:type "string", :format "date-time"}
                         :gen/gen (gen/fmap #(.toInstant ^Date %) (mg/generator inst?))}})))

(m/form [:map [:x instant]])
; => [:map [:x instant]]

(m/validate instant (Instant/now))
;=> true

(-> [:map [:x instant]]
    (m/explain {:x "kikka"})
    (me/humanize))
; => {:x ["should be instant"]}

(json-schema/transform [:map [:x instant]])
;{:type "object"
; :properties {:x {:type "string"
;                  :format "date-time"}}
; :required [:x]}

(m/decode
  [:map [:x instant]]
  {:x "2021-02-09T13:49:44.419Z"}
  (mt/json-transformer))
; => {:x #object[java.time.Instant 0x5aed36d3 "2021-02-09T13:49:44.419Z"]}

(mg/generate [:map [:x instant]])
; => {:x #object[java.time.Instant 0x4878fa62 "1970-01-01T00:00:00.295Z"]}

ikitommi13:02:57

e.g. m/-simple-typeallows to (easily?) build custom schemas that cover all the aspect: transforming, humanized errors, generators, json-schema, etc.

ikitommi13:02:17

much of the core itself is built on top of that.

ikitommi13:02:36

:type-properties allow one to hide implementation details. one could do the same fully using normal schema properties, but all the details would be in your face when you look at the schema form.

danielneal14:02:59

thanks @ikitommi, that looks great