malli icon indicating copy to clipboard operation
malli copied to clipboard

Modelling schemas defined in terms of map entries

Open bsless opened this issue 4 years ago • 3 comments

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?

bsless avatar May 31 '21 16:05 bsless

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

bsless avatar May 31 '21 16:05 bsless

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

ikitommi avatar Jul 16 '21 14:07 ikitommi

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]

bsless avatar Jul 17 '21 08:07 bsless