malli
malli copied to clipboard
Modelling schemas defined in terms of map entries
A good use case would be writing the schema for the formal specifications of JSON Schema: https://cswr.github.io/JsonSchema/spec/grammar/
Looking at the first part of the definition:
JSDoc := { ( id, )? ( defs, )? JSch }
id := "id": "uri"
defs := "definitions": { kSch (, kSch)*}
kSch := kword: { JSch }
JSch := ( res (, res)*)
res := type | strRes | numRes | arrRes | objRes | multRes | refSch | title | description
type := "type" : ([typename (, typename)*] | typename)
typename := "string" | "integer" | "number" | "boolean" | "null" | "array" | "object"
title := "title": string
description := "description": string
We can see the schema is defined in terms of key-value pairs, including minimum counts for certain categories of those pairs.
Does malli support these type or definitions or is a new type of schema required?
Note: It can be implemented poorly using:
(def Foo [:tuple [:= :a] int?])
(def Bar [:tuple [:= :b] boolean?])
(def Quux [:or Foo Bar])
(def Mm [:-map {:min 1} Quux])
with :-map schema being:
:-map (-collection-schema {:type :map, :pred map?, :empty {}, :in (fn [_ x] x)})
But I doubt it's an optimal solution
malli currently supports this partially using registry references (only strings & qualified keywords):
(m/validate
[:map {:registry {::a int?
::b boolean?}}
::a ::b]
{::a 1, ::b true})
; => true
... or manually contructing the schema:
(defn ->map [& entries]
(m/schema (into [:map] entries)))
(->map [:a int?] [:b boolean?])
; ; => [:map [:a int?] [:b boolean?]]
we do need a declarative way to say the mallii-keys-relations, but do we need a declarative way to construct map schemas from entry-pairs?
It would be easy to add a new:entry type to malli (to be able to register entires) or even a :fragment (to allow to inject partial schemas into AST), but adding stuff can make the schemas harder to understand.
entries:
(m/validate
[:map {:registry {::foo [:entry [:a int?]]
::bar [:entry [:b boolean?]]}}
::foo ::bar]
{::a 1, ::b true})
; => true
fragments:
(m/validate
[:schema {:registry {::schema [:fragment :map]
::props [:fragment {:closed true}]
::child1 [:fragment [:a int?]]
::child2 [:fragment [:b boolean?]]}}
[::schema ::props ::child1 ::clild2]]
{::a 1, ::b true})
; => true
Adding fragments might be the milllion dollar mistake of malli...
One thing I'm missing wrt entries is the ability to say "the map should contain at least one but could be more", for example"
::a [:or [:entry [:a int?]] [:entry [:b boolean?]]]
Then
[:map-of {:min 1} ::a]