malli
malli copied to clipboard
:object schema?
From a discussion in slack. Malli could have a yup object-like schema:
[:object
{"name" :string
"age" [:and :number :positive :integer]
"email" [:and :key/optional :string :email]
"website" [:and :key/optional :string :url]
"createdOn" [:and :key/optional :date {:default "2020-18-10"}]
"address" [:object
{"street" [:string {:min 1}]
"zip" :int}]}]
this might be a excellent or really bad idea, but would anyway make transition from tools like data-spec simple. This would work with both data-spec and with... litemalli?
["/plus"
{:get {:summary "plus with spec query parameters"
:parameters {:query {:x int?, :y int?}}
:responses {200 {:body {:total int?}}}
:handler handle-plus}]
, given that reitit just wraps the map-defined schemas in :object.
I can't seem to find the issue that was discussing this - but I'd like to echo sentiment seen elsewhere that this fundamentally seems like an alternative syntax for a :map schema, in which case the beauty of malli (schemas as data) should just be used and this should just be a utility function, as opposed to a new primitive.
I have wanted this (with reitit) and have written the following functions (based off an old version of malli, so, might not be perfectly up to date)
(defn ->schema
"Expands a `short-hand` schema into a schema vector."
[short-hand]
(cond
(map? short-hand) (into [:map]
(u/map-vals ->schema)
short-hand)
(and (vector? short-hand)
(= 1 (count short-hand))) [:sequential (first short-hand)]
:else short-hand))
(defn error
"Returns an error schema for the provided schema short-hand"
[short-hand]
{:body [:map [:error (->schema short-hand)]]})
(defn error-message
"Returns an error message schema"
[]
(error {:message string?}))
(defn data
"Returns a data schema for the provided schema short-hand"
[short-hand]
{:body [:map [:data (->schema short-hand)]]})
(http/router
"/users"
[""
{:post {:parameters {:body :user/new}
:responses {403 (error-message)
422 (error-message)
201 (data {:key string?}) }
:handler (partial handlers/create-user! web)}}])
Also worth noting is that if someone doesn't want to call the utility function themselves, it can be added as a :compile middleware to reitit. To me this is the best of both worlds, and perfectly demonstrates the beauty of both reitit and malli. Retit allows specifying the ability to transform the route map during compile time, while malli can be easily manipulated because schemas are just data. In my opinion, making it more complicated than that might not be worth it. If you wanted to provide batteries, perhaps reitit-malli could just have an compile-time interceptor that expanded the short hand.