malli

https://github.com/metosin/malli :malli:
danielneal 2021-02-09T11:48:46.079400Z

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

danielneal 2021-02-09T11:49:18.079800Z

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

danielneal 2021-02-09T11:49:39.080300Z

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

ikitommi 2021-02-09T11:51:00.081500Z

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]}

ikitommi 2021-02-09T11:55:57.082100Z

what do you mean by “also has validation”?

danielneal 2021-02-09T11:56:56.082700Z

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

ikitommi 2021-02-09T11:57:45.083200Z

that would return an Instant, not string.

ikitommi 2021-02-09T11:59:15.084100Z

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]}

danielneal 2021-02-09T11:59:46.084400Z

ah, that would be ideal

ikitommi 2021-02-09T12:00:15.085Z

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

danielneal 2021-02-09T12:04:16.086400Z

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.

danielneal 2021-02-09T12:04:41.086800Z

Is what :string/decode is used for

ikitommi 2021-02-09T13:53:51.087300Z

@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"]}

ikitommi 2021-02-09T13:54:57.088300Z

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

ikitommi 2021-02-09T13:55:17.088700Z

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

ikitommi 2021-02-09T13:56:36.089900Z

: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.

danielneal 2021-02-09T14:18:59.090400Z

thanks @ikitommi, that looks great