Skip to content

Commit

Permalink
open :multi dispatch
Browse files Browse the repository at this point in the history
  • Loading branch information
frenchy64 committed Jan 14, 2025
1 parent 5e18221 commit 8429b1f
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 1 deletion.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Data-driven Schemas for Clojure/Script and [babashka](#babashka).
- [Persisting schemas](#persisting-schemas), even [function schemas](#serializable-functions)
- Immutable, Mutable, Dynamic, Lazy and Local [Schema Registries](#schema-registry)
- [Schema Transformations](#schema-Transformation) to [JSON Schema](#json-schema), [Swagger2](#swagger2), and [descriptions in english](#description)
- [Multi-schemas](#multi-schemas), [Recursive Schemas](#recursive-schemas) and [Default values](#default-values)
- [Closed](#multi-schemas) and [Open](#open-multi-schemas) dispatch Multi schemas, [Recursive Schemas](#recursive-schemas) and [Default values](#default-values)
- [Function Schemas](docs/function-schemas.md) with dynamic and static schema checking
- Integrates with both [clj-kondo](#clj-kondo) and [Typed Clojure](#static-type-checking-via-typed-clojure)
- Visualizing Schemas with [DOT](#dot) and [PlantUML](#plantuml)
Expand Down Expand Up @@ -1985,6 +1985,26 @@ Any function can be used for `:dispatch`:
; :address {:country :finland}}
```

## Open Multi schemas

`:multi` schemas can be made open to extension by using [Mutable registries](#mutable-registry).

```clojure
(require '[malli.core :as m] '[malli.registry :as mr] '[malli.util :as mu])

(def registry*
(atom {::open (m/schema [:multi {:dispatch :type}])}))

(defn extend-multi! [name entry]
(swap! registry* update name mu/extend-multi entry))

(extend-multi! ::open [:sized [:map [:type keyword?] [:size int?]]])
(extend-multi! ::open [:human [:map [:type keyword?] [:name string?] [:address [:map [:country keyword?]]]]])

(m/validate ::open {:type :sized, :size 10} {:registry (mr/mutable-registry registry*)})
; => true
```

## Recursive schemas

To create a recursive schema, introduce a [local registry](#local-registry) and wrap all recursive positions in the registry with `:ref`. Now you may reference the recursive schemas in the body of the schema.
Expand Down
19 changes: 19 additions & 0 deletions src/malli/util.cljc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns malli.util
(:refer-clojure :exclude [merge select-keys find get get-in dissoc assoc update assoc-in update-in keys])
(:require [clojure.core :as c]
[malli.impl.util :as miu]
[malli.core :as m]))

(declare path->in find)
Expand Down Expand Up @@ -373,6 +374,24 @@
(apply f (get s k) args))))]
(up schema ks f args)))

(defn extend-multi
"Extend a :multi schema. Overwrites existing entry by = dispatch value if already present.
If not present, added before the default entry (if any)."
([?schema e] (extend-multi ?schema e nil))
([?schema e options]
(let [s (m/schema ?schema options)
c (m/children s)
[before-default after-default] (map vec (split-with (complement m/-default-entry) c))
[d] e
i (some (fn [i]
(when (= d (first (nth c i)))
i))
(range (count c)))
c (if i
(c/assoc c i e)
(-> before-default (conj e) (into after-default)))]
(m/-set-children s c))))

;;
;; Schemas
;;
Expand Down
18 changes: 18 additions & 0 deletions test/malli/util_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -1124,3 +1124,21 @@
#?(:clj Exception, :cljs js/Error)
#":malli\.core/child-error"
(m/schema :union {:registry (merge (mu/schemas) (m/default-schemas))}))))

(def extend-multi-reg (atom {::multi (m/schema [:multi {:dispatch identity} [:a :any] [::m/default :any]])}))
(swap! extend-multi-reg update ::multi mu/extend-multi [:a number?])
(swap! extend-multi-reg update ::multi mu/extend-multi [::m/default number?])

(deftest extend-multi-test
(is (= [:multi {:dispatch identity} [:a :any]]
(m/form (mu/extend-multi [:multi {:dispatch identity}] [:a :any]))))
(is (= [:multi {:dispatch identity} [:a 'number?]]
(m/form (mu/extend-multi [:multi {:dispatch identity} [:a :any]] [:a number?]))))
(is (= [:multi {:dispatch identity} [:b :any] [:a :any]]
(m/form (mu/extend-multi [:multi {:dispatch identity} [:b :any]] [:a :any]))))
(is (= [:multi {:dispatch identity} [::m/default 'number?]]
(m/form (mu/extend-multi [:multi {:dispatch identity} [::m/default :any]] [::m/default number?]))))
(is (= [:multi {:dispatch identity} [:a :any] [:malli.core/default 'number?]]
(m/form (mu/extend-multi [:multi {:dispatch identity} [::m/default number?]] [:a :any]))))
(is (= [:multi {:dispatch identity} [:a 'number?] [:malli.core/default 'number?]]
(m/form (m/deref (m/schema ::multi {:registry (mr/mutable-registry extend-multi-reg)}))))))

0 comments on commit 8429b1f

Please sign in to comment.