Skip to content

Commit fbf78df

Browse files
committed
Merge branch 'master' into ref-validator-knot
2 parents 94a49a2 + 5fe9bf8 commit fbf78df

File tree

5 files changed

+181
-50
lines changed

5 files changed

+181
-50
lines changed

.github/workflows/clojure.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
matrix:
1414
# Supported Java versions: LTS releases and latest
15-
jdk: [8, 11, 17, 21]
15+
jdk: [8, 11, 17, 21, 25]
1616
clojure: [11, 12]
1717

1818
name: Clojure ${{ matrix.clojure }} (Java ${{ matrix.jdk }})
@@ -113,11 +113,11 @@ jobs:
113113
runs-on: ubuntu-latest
114114
steps:
115115
- uses: actions/checkout@v5
116-
- name: Setup Java 11
116+
- name: Setup Java 25
117117
uses: actions/setup-java@v5
118118
with:
119119
distribution: zulu
120-
java-version: 11
120+
java-version: 25
121121
- uses: actions/cache@v4
122122
with:
123123
path: |

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Malli is in well matured [alpha](README.md#alpha).
1717
## UNRELEASED
1818

1919
* Robust `:and` parser, add `:andn` [#1182](https://github.com/metosin/malli/pull/1182)
20+
* Correctly handle recursive schemas [#1234](https://github.com/metosin/malli/pull/1234)
21+
* Add JDK 25 to CI [#1233](https://github.com/metosin/malli/pull/1233)
2022

2123
## 0.19.2 (2025-10-06)
2224

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ So, we decided to spin out our own library, which would do all the things we fee
7474

7575
Malli requires Clojure 1.11 or ClojureScript 1.11.51.
7676

77-
Malli is tested with the LTS releases Java 8, 11, 17 and 21.
77+
Malli is tested with the LTS releases Java 8, 11, 17, 21 and 25.
7878

7979
## Quickstart
8080

src/malli/core.cljc

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
(-distribute-to-children [this f options]))
9898

9999
(defprotocol ParserInfo
100-
(-parser-info [this]))
100+
(-parser-info [this opts]))
101101

102102
(defn -ref-schema? [x] (#?(:clj instance?, :cljs implements?) malli.core.RefSchema x))
103103
(defn -entry-parser? [x] (#?(:clj instance?, :cljs implements?) malli.core.EntryParser x))
@@ -119,9 +119,9 @@
119119
(throw (ex-info "Not distributive" {:schema this})))
120120

121121
ParserInfo
122-
(-parser-info [this]
122+
(-parser-info [this opts]
123123
(when (-ref-schema? this)
124-
(-parser-info (-deref this))))
124+
(-parser-info (-deref this) opts)))
125125

126126
RegexSchema
127127
(-regex-op? [_] false)
@@ -344,10 +344,20 @@
344344

345345
(defn -create-cache [_options] (atom {}))
346346

347+
(defn -lookup-or-update-cache [c k f]
348+
(or (@c k)
349+
(let [r (f)]
350+
(swap! c assoc k r)
351+
r)))
352+
347353
(defn -cached [s k f]
348354
(if (-cached? s)
355+
;; inlined (-lookup-or-update-cache (-cache s) k #(f s)) to avoid extra thunk
349356
(let [c (-cache s)]
350-
(or (@c k) ((swap! c assoc k (f s)) k)))
357+
(or (@c k)
358+
(let [r (f s)]
359+
(swap! c assoc k r)
360+
r)))
351361
(f s)))
352362

353363
;;
@@ -787,7 +797,7 @@
787797
(-get [_ _ default] default)
788798
(-set [this key _] (-fail! ::non-associative-schema {:schema this, :key key}))
789799
ParserInfo
790-
(-parser-info [_] {:simple-parser true})
800+
(-parser-info [_ _] {:simple-parser true})
791801
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))])))))
792802
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))))
793803

@@ -817,22 +827,30 @@
817827
(let [children (-vmap #(schema % options) children)
818828
form (delay (-simple-form parent properties children -form options))
819829
cache (-create-cache options)
820-
transforming-parser (delay
821-
(let [transforming-parsers (or (when-some [[_ i] (find properties :parse/transforming-child)]
822-
(cond
823-
(= :none i) []
824-
(and (nat-int? i) (< i (count children))) [i]
825-
:else (-fail! ::and-schema-invalid-parse-property {:schema @form})))
826-
(into []
827-
(keep-indexed
828-
(fn [i c]
829-
(when-not (-> c -parser-info :simple-parser)
830-
i)))
831-
children))]
832-
(when (next transforming-parsers)
833-
(-fail! ::and-schema-multiple-transforming-parsers {:schema @form}))
834-
(peek transforming-parsers)))
835-
->parsers (fn [f] (into [] (map-indexed (fn [i c] (if (= @transforming-parser i) (f c) (-simple-parser c)))) children))]
830+
->transforming-parser-idx (fn [opts]
831+
(let [transforming-parsers (or (when-some [[_ i] (find properties :parse/transforming-child)]
832+
(cond
833+
(= :none i) []
834+
(and (nat-int? i) (< i (count children))) [i]
835+
:else (-fail! ::and-schema-invalid-parse-property {:schema @form})))
836+
(into []
837+
(keep-indexed
838+
(fn [i c]
839+
(when-not (-> c (-parser-info opts) :simple-parser)
840+
i)))
841+
children))]
842+
(when (next transforming-parsers)
843+
(-fail! ::and-schema-multiple-transforming-parsers {:schema @form}))
844+
(peek transforming-parsers)))
845+
cached-transforming-parser-idx (delay (-lookup-or-update-cache cache ::transforming-parser-idx #(->transforming-parser-idx nil)))
846+
->parsers (fn [f]
847+
(let [transforming-parser-idx @cached-transforming-parser-idx]
848+
(into [] (map-indexed
849+
(fn [i c]
850+
(if (= i transforming-parser-idx)
851+
(f c)
852+
(-simple-parser c))))
853+
children)))]
836854
^{:type ::schema}
837855
(reify
838856
Schema
@@ -843,7 +861,7 @@
843861
(fn explain [x in acc] (reduce (fn [acc' explainer] (explainer x in acc')) acc explainers))))
844862
(-parser [this]
845863
;; non-iteratively parse x left-to-right. return result of transforming parser, or x.
846-
(let [pi @transforming-parser
864+
(let [pi @cached-transforming-parser-idx
847865
parsers (->parsers -parser)
848866
nchildren (count children)]
849867
(fn [x]
@@ -858,7 +876,7 @@
858876
(-unparser [this]
859877
;; unparse x' with transforming parser (if any), then non-iteratively unparse x with remaining parsers, left-to-right
860878
;; return x if all results are equal.
861-
(let [pi @transforming-parser
879+
(let [pi @cached-transforming-parser-idx
862880
unparsers (->parsers -unparser)
863881
unparser (get unparsers pi identity)
864882
nchildren (count children)]
@@ -887,9 +905,9 @@
887905
(-get [_ key default] (get children key default))
888906
(-set [this key value] (-set-assoc-children this key value))
889907
ParserInfo
890-
(-parser-info [_] (if-some [i @transforming-parser]
891-
(-parser-info (nth children i))
892-
{:simple-parser true}))
908+
(-parser-info [_ opts] (if-some [i (->transforming-parser-idx opts)]
909+
(-parser-info (nth children i) opts)
910+
{:simple-parser true}))
893911
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
894912
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
895913

@@ -1012,7 +1030,7 @@
10121030
(-get [_ key default] (get children key default))
10131031
(-set [this key value] (-set-assoc-children this key value))
10141032
ParserInfo
1015-
(-parser-info [_] {:simple-parser (every? (-comp :simple-parser -parser-info) children)})
1033+
(-parser-info [_ opts] {:simple-parser (every? (-comp :simple-parser #(-parser-info % opts)) children)})
10161034
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
10171035
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
10181036

@@ -1121,7 +1139,7 @@
11211139
(-get [_ key default] (get children key default))
11221140
(-set [this key value] (-set-assoc-children this key value))
11231141
ParserInfo
1124-
(-parser-info [_] {:simple-parser true})
1142+
(-parser-info [_ _] {:simple-parser true})
11251143
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
11261144
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
11271145

@@ -1197,6 +1215,7 @@
11971215
cache (-create-cache options)
11981216
default-schema (delay (some-> entry-parser (-entry-children) (-default-entry-schema) (schema options)))
11991217
explicit-children (delay (cond->> (-entry-children entry-parser) @default-schema (remove -default-entry)))
1218+
simple-default-parser? (fn [opts] (-> @default-schema (-parser-info opts) :simple-parser boolean))
12001219
->parser (fn [this f]
12011220
(let [keyset (-entry-keyset (-entry-parser this))
12021221
default-parser (some-> @default-schema (f))
@@ -1215,7 +1234,7 @@
12151234
(if optional m (reduced ::invalid))))))
12161235
@explicit-children)
12171236
default-parser
1218-
(cons (let [simple (-> @default-schema -parser-info :simple-parser boolean)]
1237+
(cons (let [simple (-lookup-or-update-cache cache ::simple-default-parser? #(simple-default-parser? nil))]
12191238
(fn [m]
12201239
(let [m' (default-parser
12211240
(reduce (fn [acc k] (dissoc acc k)) m (keys keyset)))]
@@ -1319,7 +1338,7 @@
13191338
(-get [this key default] (-get-entries this key default))
13201339
(-set [this key value] (-set-entries this key value))
13211340
ParserInfo
1322-
(-parser-info [_] {:simple-parser (every? (-comp :simple-parser -parser-info peek) (-entry-children entry-parser))})
1341+
(-parser-info [_ opts] {:simple-parser (every? #(-> % peek (-parser-info opts) :simple-parser) (-entry-children entry-parser))})
13231342
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
13241343
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
13251344

@@ -1343,10 +1362,10 @@
13431362
form (delay (-simple-form parent properties children -form options))
13441363
cache (-create-cache options)
13451364
validate-limits (-validate-limits min max)
1346-
simple-parser (delay (every? (-comp :simple-parser -parser-info) children))
1365+
simple-parser? (fn [opts] (every? (-comp :simple-parser #(-parser-info % opts)) children))
13471366
->parser (fn [f] (let [key-parser (f key-schema)
13481367
value-parser (f value-schema)
1349-
simple @simple-parser]
1368+
simple (-lookup-or-update-cache cache ::simple-parser? #(simple-parser? nil))]
13501369
(fn [x]
13511370
(if (map? x)
13521371
(reduce-kv (fn [acc k v]
@@ -1415,7 +1434,7 @@
14151434
(-get [_ key default] (get children key default))
14161435
(-set [this key value] (-set-assoc-children this key value))
14171436
ParserInfo
1418-
(-parser-info [_] {:simple-parser @simple-parser})
1437+
(-parser-info [_ opts] {:simple-parser (simple-parser? opts)})
14191438
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
14201439
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
14211440

@@ -1464,8 +1483,11 @@
14641483
validate-limits (if bounded
14651484
(-validate-bounded-limits (c/min bounded (or max bounded)) min max)
14661485
(-validate-limits min max))
1486+
simple-parser? (fn [opts]
1487+
(or (boolean bounded)
1488+
(-> schema (-parser-info opts) :simple-parser boolean)))
14671489
->parser (fn [f g] (let [child-parser (f schema)
1468-
simple (-> schema -parser-info :simple-parser boolean)]
1490+
simple (-lookup-or-update-cache cache ::simple-parser? #(simple-parser? nil))]
14691491
(fn [x]
14701492
(cond
14711493
(not (fpred x)) ::invalid
@@ -1544,8 +1566,7 @@
15441566
(-get [_ _ _] schema)
15451567
(-set [this _ value] (-set-children this [value]))
15461568
ParserInfo
1547-
(-parser-info [_] (cond-> (-parser-info schema)
1548-
bounded (assoc :simple-parser true)))
1569+
(-parser-info [_ opts] {:simple-parser (simple-parser? opts)})
15491570
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))))
15501571
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
15511572

@@ -1621,7 +1642,7 @@
16211642
(-get [_ key default] (get children key default))
16221643
(-set [this key value] (-set-assoc-children this key value))
16231644
ParserInfo
1624-
(-parser-info [_] {:simple-parser (every? (-comp :simple-parser -parser-info) children)})
1645+
(-parser-info [_ opts] {:simple-parser (every? #(-> % (-parser-info opts) :simple-parser) children)})
16251646
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
16261647
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
16271648

@@ -1668,7 +1689,7 @@
16681689
(-get [_ key default] (get children key default))
16691690
(-set [this key value] (-set-assoc-children this key value))
16701691
ParserInfo
1671-
(-parser-info [_] {:simple-parser true})
1692+
(-parser-info [_ _] {:simple-parser true})
16721693
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
16731694
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
16741695

@@ -1722,7 +1743,7 @@
17221743
(-get [_ key default] (get children key default))
17231744
(-set [this key value] (-set-assoc-children this key value))
17241745
ParserInfo
1725-
(-parser-info [_] {:simple-parser true})
1746+
(-parser-info [_ _] {:simple-parser true})
17261747
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
17271748
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
17281749

@@ -1771,7 +1792,7 @@
17711792
(-get [_ key default] (get children key default))
17721793
(-set [this key value] (-set-assoc-children this key value))
17731794
ParserInfo
1774-
(-parser-info [_] {:simple-parser true})
1795+
(-parser-info [_ _] {:simple-parser true})
17751796
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
17761797
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
17771798

@@ -1822,7 +1843,7 @@
18221843
(-set-children this [value])
18231844
(-fail! ::index-out-of-bounds {:schema this, :key key})))
18241845
ParserInfo
1825-
(-parser-info [_] (-parser-info schema))
1846+
(-parser-info [_ opts] (-parser-info schema opts))
18261847
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
18271848
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
18281849

@@ -1905,6 +1926,8 @@
19051926
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
19061927
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
19071928

1929+
;; returns an identifier for the :ref schema in the context of its dynamic scope.
1930+
;; useful for detecting cycles.
19081931
(defn -identify-ref-schema [schema]
19091932
{:scope (-> schema -options -registry mr/-schemas)
19101933
:name (-ref schema)})
@@ -2006,6 +2029,13 @@
20062029
(-regex-unparser [this] (-fail! ::potentially-recursive-seqex this))
20072030
(-regex-transformer [this _ _ _] (-fail! ::potentially-recursive-seqex this))
20082031
(-regex-min-max [this _] (-fail! ::potentially-recursive-seqex this))
2032+
ParserInfo
2033+
(-parser-info [this opts]
2034+
(let [cycles (::parser-info-cycles opts #{})
2035+
ref-id (-identify-ref-schema this)]
2036+
(if (cycles ref-id)
2037+
{:simple-parser true}
2038+
(-parser-info (-deref this) (assoc opts ::parser-info-cycles (conj cycles ref-id))))))
20092039
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
20102040
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))]))))
20112041

@@ -2188,7 +2218,7 @@
21882218
(-get [_ key default] (get children key default))
21892219
(-set [this key value] (-set-assoc-children this key value))
21902220
ParserInfo
2191-
(-parser-info [_] {:simple-parser true})
2221+
(-parser-info [_ _] {:simple-parser true})
21922222
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
21932223
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
21942224

@@ -2262,7 +2292,7 @@
22622292
(-get [_ key default] (get children key default))
22632293
(-set [this key value] (-set-assoc-children this key value))
22642294
ParserInfo
2265-
(-parser-info [_] {:simple-parser true})
2295+
(-parser-info [_ _] {:simple-parser true})
22662296
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-schema this writer opts))]))))
22672297
#?@(:cljs [IPrintWithWriter (-pr-writer [this writer opts] (pr-writer-into-schema this writer opts))])))
22682298

0 commit comments

Comments
 (0)